跳到主要内容

91 篇博文 含有标签「后端」

后端开发技术文章

查看所有标签

Go 语言中fmtPrintln 和 println 的区别

· 阅读需 2 分钟
素明诚
Full stack development

来源

  • fmt.Println 是 Go 语言标准库中 fmt 包的一部分。它是一个公共函数,可在任何 Go 程序中使用。
  • println 是一个内建函数,它不属于任何包,是 Go 语言低级操作的一部分。通常,println 主要用于调试目的。

功能性

  • fmt.Println 提供了丰富的功能。除了打印字符串,它还可以用来打印各种数据类型,并且能够处理格式化的字符串。此外,fmt.Println 在打印完字符串后会自动添加一个新行。
  • println 功能较为基础,它可以打印几乎所有值,但不支持格式化输出。它也会在输出后自动添加新行。

输出

  • fmt.Println 输出到标准输出(stdout),这意味着你可以很容易地重定向输出到其他位置,例如一个文件。
  • println 可能输出到标准错误输出(stderr),这取决于具体实现,且它的行为在不同的 Go 版本和平台上可能不同。

性能

  • fmt.Println 由于进行了更多的内部处理(例如接口转换和格式化),可能比 println 慢一些。
  • println 更快,因为它是一个简单的低级内建函数,没有额外的格式化开销。

使用场景

  • fmt.Println 是在生产环境中打印信息的推荐方式,因为它的行为明确且一致。
  • println 主要用于调试阶段,不推荐在生产代码中使用,因为它的实现在不同系统和 Go 的版本中可能会有所不同。

Java 受检Checked异常和非受检Unchecked异常

· 阅读需 2 分钟
素明诚
Full stack development

受检异常(Checked Exceptions)

  • 受检异常是那些在编译期间 Java 编译器要求必须处理(要么用 try-catch 捕获,要么在方法签名中用 throws 子句声明)的异常。
  • 它们继承自java.lang.Exception类,但不包括java.lang.RuntimeException类的子类。
  • 受检异常通常是由外部错误条件引起的,这些条件在程序运行时可能经常发生,程序员预见到这些异常情况并且被期望在编译阶段就处理它们。例如,试图根据指定的文件路径打开一个文件时可能会抛出FileNotFoundException,或者由于网络问题导致的IOException

非受检异常(Unchecked Exceptions)

  • 非受检异常是编译器不要求强制处理的异常。它们要么是由编程错误引起的(如访问 null 对象的成员、数组越界),要么是程序应该在运行时处理的异常。
  • 非受检异常包括java.lang.RuntimeException的所有子类和java.lang.Error的所有子类。RuntimeException是那些可能在 Java 虚拟机正常操作期间抛出的异常的超类。
  • 常见的RuntimeException包括NullPointerException(当应用试图在需要对象的地方使用 null 时,抛出此异常)、IndexOutOfBoundsException(指示某排序索引(如数组、字符串或向量)越界时抛出)等。
  • Error是指那些通常不被应用程序捕获的严重问题,例如OutOfMemoryErrorStackOverflowError等。

如何判断异常类型

  • 如果异常是 java.lang.RuntimeException 的子类,或者是 java.lang.Error,那它就是一个非受检异常。
  • 如果异常是 java.lang.Exception 的子类,但不是 java.lang.RuntimeException 的子类,那它就是一个受检异常

Maven 依赖是什么

· 阅读需 1 分钟
素明诚
Full stack development

拿 ruoyi 的依赖项举例子

io.springfox:springfox-boot-starter:3.0.0
io.swagger:swagger-models:1.6.2
com.mysql:mysql-connector-j:8.0.33
com.ruoyi:ruoyi-framework:3.8.6
com.ruoyi:ruoyi-quartz:3.8.6
com.ruoyi:ruoyi-generator:3.8.6

io.springfox:springfox-boot-starter:3.0.0

  • Springfox 是一个生成 Swagger API 文档的库。这个特定的依赖是 Spring Boot 的起步依赖,它允许你轻松地在 Spring Boot 项目中集成 Springfox。

io.swagger:swagger-models:1.6.2

  • 这是 Swagger 的模型库,提供了定义和操作 Swagger API 文档的类。

com.mysql:mysql-connector-j:8.0.33

  • 这是 MySQL 的 JDBC 驱动,允许 Java 程序连接到 MySQL 数据库。

com.ruoyi:ruoyi-framework:3.8.6

  • 这是 RuoYi 项目的框架部分。RuoYi 是一个基于 Spring Boot 的快速开发框架。

com.ruoyi:ruoyi-quartz:3.8.6

  • 这是 RuoYi 项目的 Quartz 部分,用于任务调度。

com.ruoyi:ruoyi-generator:3.8.6

  • 这是 RuoYi 项目的代码生成器部分,帮助你快速生成代码模板。

org.springframework.boot:spring-boot-devtools:2.5.15

  • 这是 Spring Boot 的开发工具库。它提供了一些在开发时非常有用的功能,如自动重启应用程序以查看更改、热交换等。

添加依赖

为了在 Maven 项目中添加一个依赖,你需要在 pom.xml 文件中的 <dependencies> 部分添加一个 <dependency> 元素。

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.10</version>
</dependency>

这些依赖就是你当前项目需要的库

为什么Java的int类型一定是4字节

· 阅读需 2 分钟
素明诚
Full stack development

在早期的计算机系统中,数据总线和地址总线的宽度通常是 8 位、16 位或 32 位。当 32 位的微处理器(如 Intel 的 80386)变得流行时,它们天然地支持 32 位宽的数据操作。为了最大限度地提高效率,许多数据类型,特别是整数,都被设置为这些处理器的自然字大小,即 32 位或 4 字节。

为什么 Java 的 int 类型,一定是 4 字节呢

Java 的设计哲学是“一次编写,到处运行”(write once, run anywhere,简称 WORA)。为了实现这个目标,Java 需要确保其基本数据类型在所有平台上具有相同的大小和行为。这样,无论 Java 程序在哪里运行,它都可以期望相同的数据类型行为。

为了确保 Java 程序在所有平台上都有一致的行为,Java 语言规范明确指定了基本数据类型的大小。与 C 和 C++ 等语言不同,这些语言的数据类型大小可能会根据平台和编译器而变化,Java 的 int 总是 32 位,无论在哪个平台上。

在 Java 诞生的时代(1990 年代中期),许多流行的计算机都使用 32 位的架构。定义 int 为 32 位使得 Java 可以在这些机器上高效运行,同时保持了足够的整数范围(从-2,147,483,648 到 2,147,483,647)来满足大多数应用的需求。所以 Java 的 int 类型一定是 4 字节。

Java泛型中的默认类型及基本类型的自动装箱和拆箱机制

· 阅读需 1 分钟
素明诚
Full stack development

泛型机制

  1. 如果没有进行具体类型的传入,默认使用 Object 进行装箱。
  2. 取值的时候自动拆箱为基本类型。

举例

package com.Generics;

public class Box<T> {
private T value;

public Box () {
}

public Box (T value) {
this.value = value;
}

public T getValue () {
return value;
}

public void setValue (T value) {
this.value = value;
}
}

使用

package com.Generics;

public class BoxTest {
public static void main (String[] args) {
Box genericBox = new Box(); // 不指定泛型类型,默认为Object
genericBox.setValue("Hello"); // 可以存储任何类型的对象
genericBox.setValue(123); // 也可以存储一个int

Object genericValue = genericBox.getValue(); // 返回值类型为Object
System.out.println(genericValue);

Box<Integer> intBox = new Box<>();
intBox.setValue(42); // 自动装箱:将int转换为Integer

int intValue = intBox.getValue(); // 自动拆箱:将Integer转换为int
System.out.println(intValue);

}
}

Object genericValue = genericBox.getValue()这段代码没有报错就可以判断出默认类型为Object

Maven 插件

· 阅读需 3 分钟
素明诚
Full stack development

什么是 Maven 插件?

Maven 插件是一组预打包的 Maven 扩展,它们提供了用于构建和管理项目的功能。每个插件都由一个或多个目标组成,这些目标代表了可执行的任务。

常见的 Maven 插件

maven-compiler-plugin

  • 用途: 编译 Java 源代码。
  • 绑定生命周期阶段包括:compile (编译主源代码) 和 testCompile (编译测试源代码)。

maven-surefire-plugin

  • 用途: 执行单元测试。
  • 绑定生命周期阶段是 test,用于运行单元测试并生成报告。

maven-jar-plugin

  • 用途: 构建 JAR 文件。
  • 绑定生命周期阶段是 jar,用于将编译后的代码打包为 JAR 文件。

maven-war-plugin

  • 用途: 构建 WAR 文件,通常用于 Java Web 应用程序。
  • 绑定生命周期阶段是 war,用于打包为 WAR 文件。

maven-install-plugin

  • 用途: 将项目的构建输出(如 JAR 或 WAR 文件)安装到本地 Maven 仓库,以便其他项目使用。
  • 绑定生命周期阶段是 install。

maven-deploy-plugin

  • 用途: 将构建输出部署到远程 Maven 仓库,通常用于与其他开发者共享或发布构建产物。
  • 绑定生命周期阶段是 deploy。

maven-clean-plugin

  • 用途: 清理构建产物,如 target 目录。
  • 绑定生命周期阶段是 clean。

maven-site-plugin

  • 用途: 生成项目文档和报告,如 Javadoc、代码覆盖率报告等。
  • 绑定生命周期阶段包括:site (生成站点文档) 和 site:deploy (部署站点文档到服务器)。

maven-failsafe-plugin

  • 用途: 执行集成测试。
  • 绑定生命周期阶段包括:integration-test 和 verify。

插件的工作方式

插件通常绑定到生命周期的某个阶段。当该阶段被执行时,与其绑定的插件目标也会被执行。例如,maven-compiler-plugin 的 compile 目标默认绑定到 compile 阶段,这意味着当 compile 阶段执行时,它也会执行。

自定义插件配置

在 pom.xml 文件中,你可以为插件提供自定义配置。这允许你改变插件的默认行为。例如,你可以更改 Java 的编译版本,或为单元测试指定特定的参数。

创建自定义插件

如果现有的插件不能满足你的需求,你可以创建自己的 Maven 插件。虽然这需要更深入的 Maven 和 Java 知识,但这为你提供了强大的自定义和扩展能力。

Maven 生命周期

· 阅读需 4 分钟
素明诚
Full stack development

Maven 的生命周期是一系列定义好的阶段,用于描述构建过程的不同步骤。当你执行一个 Maven 命令(如 mvn clean 或 mvn install),你实际上是在调用生命周期中的一个或多个阶段。

Maven 主要有三个标准的生命周期:

  1. clean:清除之前构建的项目。
  2. default(或 build):构建项目的主要生命周期。
  3. site:生成项目的站点文档。

默认生命周期 (主要用于构建和部署):

  • validate: 验证项目是否正确,并且所有必要的信息都可用。
  • initialize: 初始化构建状态,例如设置属性或创建目录。
  • generate-sources: 生成任何需要编译的源代码。
  • process-sources: 处理源代码,例如过滤任何值。
  • generate-resources: 生成资源。
  • process-resources: 复制并发送资源到目标目录,为打包做准备。
  • compile: 编译项目的源代码。
  • process-classes: 对编译后的字节码进行操作,如字节码增强。
  • generate-test-sources: 生成测试代码。
  • process-test-sources: 处理测试代码。
  • generate-test-resources: 生成测试所需的资源。
  • process-test-resources: 复制并发送测试资源到目标目录。
  • test-compile: 编译测试源代码。
  • test: 使用合适的单元测试框架运行测试。
  • prepare-package: 在实际打包之前进行必要的操作。
  • package: 将编译后的代码打包为分发格式,如 JAR、WAR。
  • pre-integration-test: 在集成测试之前进行必要的环境设置。
  • integration-test: 处理和部署包到集成测试环境中,并运行。
  • post-integration-test: 在集成测试之后进行必要的操作。
  • verify: 检查包的有效性,并确保满足质量标准。
  • install: 安装打包的项目到本地仓库,供其他项目使用。
  • deploy: 在构建环境中,将最终的包复制到远程仓库,供共享和合作。

1. 清理生命周期:

  • pre-clean: 执行清理前的需要的操作。
  • clean: 移除先前的构建产物。
  • post-clean: 执行清理后的需要的操作。

2. 站点生成生命周期:

  • pre-site: 执行生成站点前的需要的操作。
  • site: 生成项目的站点文档。
  • post-site: 执行生成站点文档后的需要的操作。
  • site-deploy: 部署生成的站点文档到指定的服务器。

执行生命周期

当你运行一个生命周期阶段时,Maven 会按顺序执行该阶段之前的所有阶段。例如,如果你执行 mvn install,Maven 会按顺序执行从 validate 到 install 的所有阶段。

生命周期和生命周期阶段

生命周期 (Lifecycle):

  • 生命周期是构建过程中的一系列阶段的集合。Maven 定义了三个主要的生命周期:default(主要用于构建项目)、clean(用于清理项目)和 site(用于生成项目的站点文档)。
  • 这些生命周期定义了一个项目从创建到部署的整个构建过程。

生命周期阶段 (Lifecycle Phase):

  • 生命周期阶段是构建过程中的一个步骤或任务。例如,compile、test 和 install 都是生命周期阶段。
  • 当你执行一个生命周期阶段时,Maven 会按顺序执行该生命周期中直到该阶段为止的所有阶段。例如,执行 mvn install 时,Maven 会按顺序执行 default 生命周期中从 validate 到 install 的所有阶段。

你不能直接添加新的生命周期。Maven 的生命周期是预定义的,它包括 default(用于构建),clean(用于清理)和 site(用于生成项目文档)这三个主要的生命周期。

但是,你可以做的是:

  1. 自定义生命周期的阶段:虽然你不能添加新的生命周期,但你可以为已有的生命周期阶段定义自定义的行为。这是通过绑定插件的目标到特定的生命周期阶段来实现的。例如,你可以绑定一个插件的目标到 compile 阶段,使得在编译时执行特定的操作。
  2. 创建自定义插件和目标:如果 Maven 的内置插件和目标不能满足你的需求,你可以创建自己的插件,并定义新的目标。然后,你可以将这些自定义目标绑定到生命周期的特定阶段。
  3. 使用 Profile:Profile 允许你为不同的构建环境和条件定义不同的设置。虽然这不是添加新的生命周期,但它可以让你根据不同的情境改变构建的行为。

pomxml 文件主要内容

· 阅读需 2 分钟
素明诚
Full stack development

项目基本信息:

  • modelVersion: POM 模型的版本。
  • groupId, artifactId, version: 定义了项目的坐标。
  • name: 项目的名称。
  • url: 项目的 URL。
  • description: 项目的描述。
  1. 项目属性 (properties): 定义了一些可以在整个pom.xml文件中重复使用的属性。
  2. 依赖管理 (dependencyManagement): 定义了项目中所有可能用到的依赖及其版本。
  3. 模块 (modules): 列出了项目的所有子模块。
  4. 构建 (build): 定义了项目的构建配置,如使用的插件。
  5. 仓库 (repositoriespluginRepositories): 定义了 Maven 从哪里获取依赖和插件。
  6. 依赖 (dependencies): 列出了项目直接依赖的库。
  7. 父项目 (parent): 定义了项目的父项目,从中继承某些配置。
  8. 配置 (profiles): 定义了多个构建配置。
  9. 报告插件 (reporting): 定义了生成项目报告的插件。
  10. 分发管理 (distributionManagement): 定义了项目的发布配置。
  11. 属性 (properties): 可以定义任意的键值对,这些键值对可以在整个pom.xml文件中使用。
  12. 组织信息 (organization): 提供有关项目所属组织的信息。
  13. 开发者和贡献者 (developerscontributors): 列出了项目的主要开发者和其他贡献者。
  14. 许可证 (licenses): 描述了项目的许可证信息。

实现 EventHub

· 阅读需 4 分钟
素明诚
Full stack development

当面试官让你实现 EventHub 或类似的事件系统时,他们主要考察以下几点:

  1. 基本的编程能力:这是显而易见的。你能否写出工作的代码,是否熟悉语言特性和基础概念。
  2. 数据结构的知识:如何高效地存储和检索事件监听器是关键。通常,哈希表(在 JavaScript 中是对象或 Map)是一个合适的选择。
  3. 设计模式:事件模型是观察者模式的一个实例。面试官可能希望你能识别并实现这一点。
  4. 异步编程:JavaScript 的事件和异步性质是其核心特性之一。对 Promise、async/await 或其他异步机制的了解和正确使用会是一个加分项。
  5. 错误处理:如何处理监听器中的错误,如何通知其他部分的应用程序,这都是重要的设计决策。
  6. 代码的可维护性和可读性:清晰、结构化、有注释的代码总是受到欢迎的。
  7. API 的设计:一个好的 EventHub 实现不仅要功能齐全,而且要易于使用。面试官可能会评估你设计的 API 是否直观和用户友好。
  8. 性能和优化:虽然初级实现可能不需要考虑这些,但面试官可能会问及性能问题,或者询问如何优化代码。
  9. 扩展性和灵活性:除了基本功能外,面试官可能还会询问如何扩展你的实现,例如添加事件组、命名空间、中间件等。
  10. 测试:对于一些高级职位或特定的公司,面试官可能会询问如何为你的 EventHub 实现编写测试。

如何编写

  1. 首先编写最基础的方法 on、emit、off,实现最基本的事件监听和触发功能
  2. 接着再在此基础上进行完善、once、bind、链式调用等
  3. 最后增加一些查看获取事件的方法

EventHub 案例

/*
* on 方法:注册事件监听器,本质就是往数组里面添加回调函数
* emit 方法:发射事件,本质就是遍历数组,执行回调函数
* off 方法:移除事件监听器,本质就是从数组里面移除回调函数
* once 方法:注册只触发一次的事件监听器,本质就是在回调函数执行后,移除该回调函数
* bind 方法:在指定上下文中注册事件监听器,本质就是利用 on 方法注册回调函数时,使用 bind 方法绑定上下文
* 链式调用:所有方法都返回 this,就支持链式调用
* */

class EventHub {
constructor() {
// 存储所有的事件及其对应的回调
this.events = {}
}

// 注册事件监听器
on(event, callback) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(callback)
return this // 支持链式调用
}

// 发射事件
emit(event, ...args) {
if (this.hasListener(event)) {
new Promise((resolve, reject) => {
const listeners = [...this.events[event]]
for (let callback of listeners) {
try {
callback(...args)
} catch (error) {
// 如果 error 事件的监听器也出错,直接拒绝 Promise
if (event === 'error') {
reject(error)
return
} else {
this.emit('error', error)
}
}
}
resolve()
}).catch(error => {
console.error('Error in emit method:', error)
})
}
return this
}

// 移除事件监听器
off(event, callback) {
if (this.hasListener(event)) {
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
return this
}

// 注册只触发一次的事件监听器
once(event, callback) {
const wrappedCallback = (...args) => {
callback(...args)
this.off(event, wrappedCallback)
}
return this.on(event, wrappedCallback) // 利用 on 方法进行注册
}

// 在指定上下文中注册事件监听器
bind(event, callback, context) {
return this.on(event, callback.bind(context))
}

// 检查指定事件是否有监听器
hasListener(event) {
return this.events[event] && this.events[event].length > 0
}

// 获取所有已注册的事件类型
getEventTypes() {
return Object.keys(this.events)
}
}

// 使用示例
// 1. 获取 EventHub 的实例
const eventHub = new EventHub()

// 2. 注册事件监听器
eventHub.on('data', data => {
console.log(data)
throw new Error('Sample error from data listener')
})

eventHub.once('data', data => {
console.log(`Once: ${data}`)
})

const errorCallback = error => {
console.log(`Error: ${error.message}`)
}
eventHub.on('error', errorCallback)

const user = {
name: 'Alice',
showName() {
console.log(`Hello, my name is ${this.name}`)
}
}
eventHub.bind('showName', user.showName, user)

// 3. 发射事件
console.log('Before emit')
eventHub.emit('data', 'Some data')
eventHub.emit('showName')
console.log('After emit')

// 4. 移除事件监听器
eventHub.off('error', errorCallback)

// 5. 检查是否有监听器
console.log('Has data listener:', eventHub.hasListener('data')) // true
console.log('Has error listener:', eventHub.hasListener('error')) // false

// 6. 获取所有的事件类型
console.log('Event types:', eventHub.getEventTypes()) // ['data', 'error', 'showName']

使用 Jest 测试 EventHub

const EventHub = require('./EventHub');  // 调整路径以匹配你的EventHub文件位置

describe('EventHub', () => {
let eventHub;

beforeEach(() => {
eventHub = new EventHub();
});

test('should register and trigger an event', () => {
const callback = jest.fn();
eventHub.on('testEvent', callback);

eventHub.emit('testEvent', 'payload');

expect(callback).toHaveBeenCalledWith('payload');
});

test('should register and trigger an event only once', () => {
const callback = jest.fn();
eventHub.once('testEvent', callback);

eventHub.emit('testEvent', 'payload');
eventHub.emit('testEvent', 'payload');

expect(callback).toHaveBeenCalledTimes(1);
});

test('should unregister an event', () => {
const callback = jest.fn();
eventHub.on('testEvent', callback);
eventHub.off('testEvent', callback);

eventHub.emit('testEvent', 'payload');

expect(callback).not.toHaveBeenCalled();
});
});

JS实现根据域名查找IP

· 阅读需 1 分钟
素明诚
Full stack development

直接上代码了,很简单,有 Node 就可以

const dns = require('dns');
const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
// 添加CORS跨域头部信息
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

// 判断URL是否以/getIp开始
if (req.url.startsWith('/getIp')) {
// 解析URL获取查询参数
const path = url.parse(req.url, true).query;
console.log(path.domain);

// DNS查询
dns.lookup(path.domain, (err, address, family) => {
if (err) {
res.writeHead(500, {'Content-Type': 'text/plain'});
res.end('获取IP地址时出错。');
console.error('DNS查询时出错:', err);
return;
}
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(address);
console.log('IP地址:', address);
console.log('IP版本:', family);
});

console.log('有人访问了我们的web服务器。');
} else {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.end('未找到');
}
});

server.listen(3636, () => {
console.log('服务器运行在 http://localhost:3636');
});