依赖

SpringBoot依赖springdoc-openapi对接口API进行注解

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.5</version>
</dependency>

配置信息application.yml中添加swagger-ui

1
2
3
springdoc:
swagger-ui:
path: swagger-ui.html

启动项目后就可以通过 https://localhost:8443/swagger-ui.html 或者https://localhost:8443/swagger-ui/index.html#/访问后端接口信息。

Swagger 注解

Swagger Annotation Tutorial

@Tag

API分组

1
@Tag(name = "OAuth2 Authorization", description = "OAuth2 登陆接口")

@Operation

用于描述API操作

  • summary:操作的摘要信息。
  • description:操作的详细描述。
  • tags:指定 @Tag 注解的对象数组,用于将操作归类到特定的分组。
  • parameters:指定 @Parameter 注解的对象数组,用于描述操作的输入参数
  • responses:指定 @ApiResponse 注解的对象数组,用于描述操作的响应结果。
  • requestBody:指定 @RequestBody 注解的对象,用于描述操作的请求体。
1
@Operation(summary = "跳转到 OAuth2 授权页面", description = "两次重定向,跳转到Github登陆页面,登陆后与后端交换Token,最后跳转到origin_url")

@Parameter

1
@Parameter(name = "origin_url", required = true, description = "OAuth2流程结束后会跳转到该URL")

@ApiResponse

1
@ApiResponse(responseCode = "301", description = "两次跳转到Github登陆窗口")

@OpenAPIDefinition

1
2
3
4
5
6
7
8
9
10
11
@OpenAPIDefinition(
info = @Info(
title = "User Manager API",
version = "1.0.0",
description = "用户信息管理API",
contact = @Contact(
name = "yingzheng",
email = "yingzhengttt@gmail.com"
)
)
)

Electron 简单来说就是一个浏览器套壳打包方案,相当于你开发了一个网页后,通过浏览器能访问这个网页;然后如果你想把网页打包成一个单独的应用的话,可以使用Electron,它内置了 chromium 和 node.js 运行环境,使用 Electron 相当于构建了一个只显示你的网页的浏览器程序。而且Electron可以通过node.js提供一些浏览器不能提供的功能。

Electron 支持 URL 加载文件加载两种方式。

  • URL加载是传入一个URL,显示URL内容。
  • 文件加载是传入HTML文件,Electron通过文件渲染内容。

理论上来说,只要能进行静态导出,就能兼容各种前端框架。同时也提供了 Next.js 动态路由的加载的方式。

创建Nextjs工程文件

1
npx create-next-app@latest electron_nextjs

创建参数全部使用默认即可(使用Typescript,使用Tailwind CSS,使用App Router)

打包Nextjs工程

package.json的scripts中已经有了build。

Nextjs打包有两种方式(需要配置next.config.js):

  • 静态模式

    1
    2
    3
    4
    5
    6
    //next.config.js
    import type {NextConfig} from "next";
    const nextConfig: NextConfig = {
    output: 'export',
    };
    export default nextConfig;
  • 动态模式

    1
    2
    3
    4
    5
    6
    //next.config.js
    import type {NextConfig} from "next";
    const nextConfig: NextConfig = {
    output: 'standalone',
    };
    export default nextConfig;

静态模式是将工程压缩到一个HTML中,Electron只需要使用文件加载加载该HTML即可。但静态模式无法进行网络请求所以只用在一些仅展示的页面中,比如博客,公司简介等。

静态导出会将内容导出到out/目录下的index.html中,后续Electron只需要加载该文件即可(本文不讨论):

1
2
// main.js
mainWindow.loadFile(path.join(__dirname, '../out/index.html'))

本文采用动态模式,即Nextjs将以standalone模式进行打包,文件被导出到.next/standalone文件夹下,其中有server.js用于启动该项目。

1
node ./server.js

为了将公共资源包也整合进.next/standalone,需要重定义package.json中的next build

1
2
3
4
//package.json
"scripts": {
"build:next": "next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/",
},

定义build:next脚本,执行next build后将public.next/static导入到 .next/standalone中。

执行npm run build:next 即可打包Nextjs项目,然后进入.next/standalone使用node ./server.js启动Nextjs项目。

至此,Nextjs的打包过程就完成了。

配置Electron

根据Electron官网,将Electron依赖加入到项目中:

1
npm install electron --save-dev

为了能同时启动Nextjs和Electron,安装concurrently依赖包

1
npm install concurrently --save-dev

创建main/main.js文件作为Electron的启动入口,以及main/preload.js作为主进程与渲染进程的桥梁。

添加包依赖,需要在main/main.js中使用,electron-is-dev用于判断是否处于dev开发过程,get-port-please是用于获取一个未被使用的端口,用于启动nextjs的standalone。

1
npm install electron-is-dev get-port-please

main/main.js

主要功能实现(第一个功能在后续Nextjs中会用到,目前没用):

  • 关闭系统自带状态栏,启用自定义按钮。(需要在前端css设置 app-region: drag,提供可拖拽区域)。
  • loadURL前进行轮训,直至nextjs服务启动,保证Electron加载URL时,Nextjs已经启动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// main/main.js
import {app, BrowserWindow} from "electron";

import isDev from "electron-is-dev";
import http from "http";
import {fileURLToPath} from 'url';
import {dirname} from 'path';
import path from "node:path";
import {getPort} from "get-port-please";
import {startServer} from "next/dist/server/lib/start-server.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

let win = null;

// 判断Nextjs是否启动成功,每一秒检查一次
const waitForNextJs = (bg_url) => {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
http.get(bg_url, (res) => {
if (res.statusCode === 200) {
clearInterval(interval);
resolve(); // Next.js 启动完成
}
}).on('error', (err) => {
console.log('Next.js not yet started, retrying...');
});
}, 1000);
});
}

// 启动Nextjs standalone包
const startNextJSServer = async () => {
try {
const nextJSPort = await getPort({portRange: [3000, 8000]});
const webDir = path.join(__dirname, "../.next/standalone/");
console.log("path: " + webDir);

await startServer({
dir: webDir,
isDev: false,
hostname: "localhost",
port: nextJSPort,
customServer: true,
allowRetry: false,
keepAliveTimeout: 5000,
minimalMode: false,
});

return nextJSPort;
} catch (error) {
console.error("Error starting Next.js server:", error);
throw error;
}
};

// BrowserWindow使用自定义状态栏
const createWindow = () => {
win = new BrowserWindow({
width: 1000,
height: 800,
minWidth: 600,
minHeight: 400,
titleBarStyle: 'hidden', //关闭系统自带状态栏
titleBarOverlay: {
color: "rgba(255, 255, 255, 0)",
symbolColor: 'rgba(255, 255, 255, 0)',
},

webPreferences: {
preload: path.join(__dirname, 'preload.js'),
}
});
// 首先加载Loading页面,等待Nextjs的启动
win.loadFile(path.join(__dirname, '../public/loading.html'));
const loadURL = async () => {
if (isDev) {
const bg_url = "http://localhost:3000";
waitForNextJs(bg_url).then(() => {
win.setTitleBarOverlay({
color: "rgba(255, 255, 255, 0)",
symbolColor: '#74b1be',
height: 40
})
win.loadURL(bg_url);
})
} else {
try {
// 启动 nextjs 打包后的 standalone 并获取启动 端口
const port = await startNextJSServer();
console.log("Next.js server started on port:", port);
win.setTitleBarOverlay({
color: "rgba(255, 255, 255, 0)",
symbolColor: '#74b1be',
height: 40
})
win.loadURL(`http://localhost:${port}`);
} catch (error) {
console.error("Error starting Next.js server:", error);
}
}
};
loadURL();

// win.webContents.openDevTools();
// win.webContents.on("did-fail-load", (e, code, desc) => {
// win.webContents.reloadIgnoringCache();
// });
}

app.disableHardwareAcceleration();
app.whenReady().then(createWindow)
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});

main/preload.js

目前没有使用

1
2
// main/preload.js

public/目录下创建loading.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--public/loading.html-->
<html>
<body>
</body>
</html>

<style>
body {
margin: 0;
height: 100vh;
width: 100vw;
background: linear-gradient(to right, red, blue);
}
</style>

设置package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "nextjs_electron",
"version": "0.1.0",
"private": true,
"main": "main/main.js",
"type": "module",
"scripts": {
"dev:next": "next dev",
"dev:electron": "electron ./main/main.js",
"dev": "concurrently --kill-others \"npm run dev:next\" \"npm run dev:electron\"",
"build:next": "next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/",
},
...
}

执行 npm run dev 同时启动Nextjs和Electron。(目前窗口时不能拖动的,后续会在Nextjs中添加)

Electron Forge打包Electron

根据Electron Forge官网教程安装打包工具:

1
2
npm install --save-dev @electron-forge/cli
npm exec --package=@electron-forge/cli -c "electron-forge import"

然后在package.json中配置需要打包的类型和平台并更新scripts

package.json

其中Scripts中"dev:nextweb": "NEXT_PUBLIC_WEB=true next dev"是为了能让Nextjs能够以WEB的形式启动而不依赖于Electron。因为Nextjs某些特性只能在Electron中才能启动,使用该变量将这些特性只有在启用Electron才加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
"name": "nextjs_electron",
"version": "0.1.0",
"private": true,
"main": "main/main.js",
"type": "module",
"scripts": {
"dev:next": "next dev",
"dev:nextweb": "NEXT_PUBLIC_WEB=true next dev",
"dev:electron": "electron ./main/main.js",
"dev": "concurrently --kill-others \"npm run dev:next\" \"npm run dev:electron\"",
"build:next": "next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/",
"build:electron": "electron-forge package",
"build": "npm run build:next && npm run build:electron",
"make": "electron-forge make",
"dist": "npm run build && npm run make",
"start": "electron-forge start",
"lint": "next lint"
},
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-deb",
"platforms": ["linux"],
"config": {
"icon": "public/chat.png",
"description": "ichat for deb",
"productDescription": "ichat for deb"
}
}
]
}
},
... ...
}

注:打包过程中,本文设置了makers.config.icon,所以需要添加图片public/chat.png,不想设置icon可以直接在package.json中删除makers.config.icon

执行npm run dist命令,即执行了Nextjs的buildelection-forge的packageelection-forge的make。最终的生成文件在/out/make/内。

注:本文生成的时Linux的deb文件,执行命令sudo apt install ./nextjs_electron_0.1.0_amd64.deb安装。

Nextjs可拖拽状态栏 和 更改主题颜色(可选)

本Nextjs项目整合

添加上述两个依赖后,需要在layout.tsx中自定义状态栏,将状态栏所在的div的CSS添加style={{"app-region": 'drag'}}。该CSS特性是Electron专属,可以让模块进行拖拽。

src/app/layout.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "./globals.css";
import {Providers} from "./providers";
import CustomBar from "@/app/component/bar/CustomBar";

export default function RootLayout({children,}: Readonly<{ children: React.ReactNode; }>) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<Providers>
<div className="h-screen w-screen flex flex-col">
<div className="bar_height w-full bg-bar_bg">
<CustomBar/>
</div>
<div className="flex-1">
{children}
</div>
</div>
</Providers>
</body>
</html>
);
}

src/app/globals.css

其中定义一些TailwindCSS自定义的字段,可以在TailwindCSS中直接使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
font-family: Arial, Helvetica, sans-serif;
margin: 0;
}

@layer components {
--header-height: 40px;

.bar_height {
height: var(--header-height);
}

.bar_icon {
height: var(--header-height);
width: 40px;
}
}

tailwind.config.ts

设置不同主题下的颜色,并设置自定义的颜色变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import type {Config} from "tailwindcss";
import {heroui} from "@heroui/react";

export default {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}"
],
darkMode: "class",
plugins: [heroui({
themes: {
light: {
colors: {
background: "#FFFFFF",
foreground: "#11181C",
bar_bg: "#d4d4d8"
}
},
dark: {
colors: {
background: "#000000",
foreground: "#ECEDEE",
bar_bg: "#3f3f46"
}
},
}
})],
} satisfies Config;

src/app/provider.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use client'

import {HeroUIProvider} from '@heroui/react'
import {ThemeProvider as NextThemesProvider} from "next-themes";

export function Providers({children}: { children: React.ReactNode }) {
return (
<HeroUIProvider>
<NextThemesProvider attribute="class" defaultTheme="system" enableSystem={true} themes={["dark", "light"]}>
{children}
</NextThemesProvider>
</HeroUIProvider>
)
}

src/app/component/bar/CustomBar.tsx

Nextjs自定状态栏,为了满足能够同时支持WEB和ELECTRON两种启动方式,定义NEXT_PUBLIC_WEB全局变量,用来动态设置是否支持某些ELECTRON特性。后面会在next.config.ts中设置该变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"use client"

import ThemeSwitcher from "@/app/component/bar/ThemeSwitcher";
import Settings from "@/app/component/bar/Settings";
import UserInfo from "@/app/component/bar/UserInfo";
import {useEffect, useState} from "react";

export default function CustomBar() {
const [web, setWeb] = useState(false);
useEffect(() => {
if (process.env.NEXT_PUBLIC_WEB === 'true') {
setWeb(true);
}
}, [])

return (
<div className="h-full w-full flex flex-end gap-2 electron_pr"
style={web ? {"padding-right": "0px"} : {"app-region": 'drag'}}>
<div className="mr-auto">bar</div>
<div className="bar_icon" style={web ? {} : {"app-region": 'no-drag'}}>
<ThemeSwitcher/>
</div>
{/*<div className="bar_icon" style={web ? {} : {"app-region": 'no-drag'}}>*/}
{/* <Settings/>*/}
{/*</div>*/}
{/*<div className="bar_icon flex items-center justify-center" style={web ? {} : {"app-region": 'no-drag'}}>*/}
{/* <UserInfo/>*/}
{/*</div>*/}
</div>
)
}

ThemeSwitcher.tsx

使用useTheme来更改Nextjs的主题,使用HeroUI的Dropdown来提供更改按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"use client";

import {Button} from "@heroui/react";
import {useTheme} from "next-themes";
import React, {useEffect, useState} from "react";
import {Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@heroui/dropdown";

const DarkIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path
d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z"></path>
</svg>
)
}

const LightIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path
d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z"></path>
</svg>
)
}

const SystemIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path
d="M4 16H20V5H4V16ZM13 18V20H17V22H7V20H11V18H2.9918C2.44405 18 2 17.5511 2 16.9925V4.00748C2 3.45107 2.45531 3 2.9918 3H21.0082C21.556 3 22 3.44892 22 4.00748V16.9925C22 17.5489 21.5447 18 21.0082 18H13Z"></path>
</svg>
)
}

export default function ThemeSwitcher() {
const [mounted, setMounted] = useState(false)
const {theme, setTheme} = useTheme()
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) return null

const toggleTheme = (key: React.Key) => {
if (key === "system") {
setTheme("system")
} else if (key === "dark") {
setTheme("dark")
} else if (key === "light") {
setTheme("light")
} else {
console.log("theme error")
}
}

return (
<Dropdown classNames={{
content: "min-w-32 w-32"
}}>
<DropdownTrigger>
<Button className="h-10 w-10 min-w-10 p-0" variant="light" isIconOnly>
{theme === "system" && <SystemIcon/>}
{theme === "light" && <LightIcon/>}
{theme === "dark" && <DarkIcon/>}
</Button>
</DropdownTrigger>
<DropdownMenu onAction={toggleTheme}
disallowEmptySelection
>
<DropdownItem key="light" startContent={<LightIcon/>}>Light</DropdownItem>
<DropdownItem key="dark" startContent={<DarkIcon/>}>Dark</DropdownItem>
<DropdownItem key="system" startContent={<SystemIcon/>}>System</DropdownItem>
</DropdownMenu>
</Dropdown>
)
};

为了布局美观,将原来的src/app/page.tsx内容删除,改为

1
2
3
4
5
export default function Home() {
return (
<div> main </div>
);
}

此时执行npm run dev就可以启动程序了,并且可以切换主题。

但执行npm run dist打包的时候会报错,CSS中没有属性app-region,因为该特性是Electron特有的,在Nextjs打包时候会报错,为了不让Nextjs进行类型认证,在next.config.ts中设置跳过类型认证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import type {NextConfig} from "next";

const nextConfig: NextConfig = {
output: 'standalone',
eslint: {
ignoreDuringBuilds: true, // 忽略 eslint 检查
},
typescript: {
ignoreBuildErrors: true, // 忽略 TypeScript 检查
},

env: {
NEXT_PUBLIC_WEB: process.env.NEXT_PUBLIC_WEB || 'false',
}
};

export default nextConfig;

String

String底层为和将char[]改成了byte[]

在 Java 9 之前,String 的底层实现使用的是 char[]数组来存储字符数据。然而,随着 Unicode 编码的普及和多语言环境的需求增加,char 类型无法满足所有情况下的字符表示要求。因此,在 Java 9 中,String 的底层实现被修改为使用 byte[]数组来存储字符数据。

解释 Java 在处理字符串拼接时如何优化性能,尤其是在 Java 9 以后的版本

Java 9 之前的优化:
编译期优化:在 Java 9 之前,编译器对字符串拼接做了优化。对于简单的字符串常量拼接,编译器会在编译期进行优化,将其直接替换为一个单独的字符串常量。例如:

String str = "Hello, " + "world!";

这行代码在编译期会被优化成:

String str = "Hello, world!";

使用 StringBuilder 进行拼接:对于包含变量的字符串拼接,Java 编译器会自动将拼接操作转换为使用 StringBuilder 进行的拼接操作。例如:

1
2
3
String str1 = "Hello";
String str2 = "world";
String result = str1 + ", " + str2 + "!";

在编译后的字节码中,上述代码会被转换为:

1
2
3
4
5
6
StringBuilder sb = new StringBuilder();
sb.append(str1);
sb.append(", ");
sb.append(str2);
sb.append("!");
String result = sb.toString();

Java 9 及以后的优化:

在 Java 9 以后,JVM 引入了基于 invokedynamic指令的字符串拼接优化,进一步提升了性能。

invokedynamic 指令:Java 9 引入了invokedynamic 指令来优化字符串拼接。编译器在处理字符串拼接时,会生成一个调用 invokedynamic 指令的字节码,而不是直接使用 StringBuilderinvokedynamic 指令在运行时决定最佳的拼接策略。

  • 减少中间对象:通过动态拼接策略,减少了中间对象的创建,降低了内存使用和 GC 压力。

  • 运行时优化:invokedynamic 指令允许 JVM 在运行时根据实际使用情况选择最佳策略,提高了代码的执行效率。

  • 更高的灵活性:使用 invokedynamic 提供了更高的灵活性,允许未来的 JVM 优化进一步提升性能,而不需要改变应用代码。

描述一下字符串常量池(String Pool)的工作原理

字符串常量池(String Pool)是 Java 中用于存储字符串字面值的一种特殊内存区域。它的主要目的是为了节省内存和提高性能。以下是字符串常量池的工作原理和它如何影响字符串实例的具体描述:

  • 字符串字面值的存储:
    • 当一个字符串字面值被创建时,如 String str = “Hello”;,JVM 会先检查字符串常量池中是否已经存在一个值为 “Hello” 的字符串。
    • 如果常量池中已经存在这个字符串,JVM 不会创建新的字符串对象,而是直接返回常量池中的引用。这意味着相同的字符串字面值在内存中只会存储一次。
  • 字符串的 intern() 方法:
    • 可以显式地将一个字符串添加到常量池中,使用 intern() 方法。例如:String str = new String(“Hello”).intern();
    • intern() 方法会检查常量池中是否存在一个值等于该字符串对象的字符串。如果存在,则返回池中的字符串引用;如果不存在,则将该字符串添加到常量池中,并返回该字符串的引用。

编译时的优化:编译器会自动将所有字符串字面值添加到常量池中。这意味着在编译时,字符串字面值就已经在常量池中,运行时直接使用。

贪心算法

贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。

分配问题

455. 分发饼干

贪心+双指针+排序

为了让尽可能多的孩子得到满足,所以让能满足的孩子所给的饼干尺寸与胃口值尽可能的接近,防止浪费。即把大于等于这个孩子饥饿度的、且大小最小的饼干给这个孩子。

先用小的饼干满足了胃口最小的孩子,然后逐渐满足胃口大的孩子。

算法思想:

  • 对胃口值和饼干尺寸升序排序
  • 从小到大逐个对胃口值进行满足,从小到大寻找第一个满足的饼干尺寸
  • 遍历到完最后一个饼干或者满足所有的胃口值结束

算法实现:

  • 排序使用Java的Arrays.sort() 算法复杂度 $O(nlog(n))$
  • 对两个数组进行联动遍历,可使用双指针分别指向胃口值数组和饼干尺寸数组
  • 使用while循环遍历
1
2
3
4
5
6
7
8
9
10
11
12
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int gindex = 0, sindex = 0;
while (sindex < s.length && gindex < g.length) {
if (g[gindex] <= s[sindex]) {
gindex++;
}
sindex++;
}
return gindex;
}

135. 分发糖果

贪心

所有的孩子站成一排,满足相邻两个孩子评分更高的孩子获得更多的糖果,即第$i$个孩子需要与相邻的两个孩子(即第$i-1$,和第$i+1$ 个孩子)进行比较。

贪心算法时将一个复杂的问题分解为多个局部问题,实现局部最优解,最后实现全局最优。

将与左右孩子的比较划分为向右遍历向左遍历

算法思想:

向右遍历:从左向右,保证右侧评分大于左侧评分的孩子获得的糖果多,即当$ratings[i+1]>ratings[i]$则$candys[i+1]>candys[i]$

向左遍历:从右向左,保证左侧评分大于右侧评分的孩子获得的糖果多,即当$ratings[i]>ratings[i+1]$则$candys[i]>candys[i+1]$

算法实现:

  • candys[]数组全部赋值为1,因为每个孩子至少获取一个糖果

  • 向右遍历:

$$
candys[i+1] = candys[i] + 1 \quad if ratings[i+1] > ratings[i]
$$

  • 向左遍历:
    $$
    candys[i] = max(candy[i], candy[i+1]+1) \quad if \quad ratings[i]> ratings[i+1]
    $$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int candy(int[] ratings) {
int[] candy = new int[ratings.length];
Arrays.fill(candy, 1);
for (int i = 0; i < ratings.length - 1; i++) {
if (ratings[i] < ratings[i + 1]) {
candy[i + 1] = candy[i] + 1;
}
}
for (int i = ratings.length - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
candy[i] = Math.max(candy[i], candy[i + 1] + 1);
}
}
return Arrays.stream(candy).sum();
}

区间问题

435. 无重叠区间

贪心

要保证移除的区间数量最少,就相当于留下的区间尽可能的多。贪心思路是选择区间的结尾越小,剩余留给其他区间的空间就越大

算法思想:

从结尾最小的区间开始遍历,去掉与其交叉的所有区间,再选择剩余中结尾最小的区间,重复操作。

算法实现:

  • intervals根据先结尾后开头的排序规则对其进行升序排序
  • 选取第一个区间,遍历找出一个区间开头大于等于第一个区间结尾的区间
  • 选取该区间,再去寻找下一个一个区间开头大于等于该区间结尾的区间
  • 依次循环
1
2
3
4
5
6
7
8
9
10
11
12
13
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[1] == b[1] ? a[0] - b[0] : a[1] - b[1]);
int ans = 0;
int curEnd = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] < curEnd) {
ans++;
} else {
curEnd = intervals[i][1];
}
}
return ans;
}

452 用最少数量的箭引爆气球

因为要扎爆所有的气球,所以每一箭需要射中尽可能多的气球。当箭射在气球的右边缘时,既能射爆该气球,又能尽可能的覆盖它后面的气球,所以每个气球的右端点很重要。

算法实现:

  1. 将所有的气球升序排序(先排右端点,若右端点相同,再根据左端点)

  2. 选择第一个气球,以其右端点为界,依次向后寻找左端点小于该边界的气球,直至找到左端点大于该边界的气球

  3. 对该气球重复第二步操作,直至扫完所有气球

如下图所示,第一个蓝色边界是第一个气球的右端点,会将第一个球和上面的大球射爆。(但在顺序扫描中,第二个小气球会先出现,因为是根据右端点排序,此时第一个边界没有碰到第二个小气球,所以边界更新为第二条蓝色边界,这时下一个气球就是上面的大球,发现大球的左端点是小于目前的第二条蓝色边界的,所以继续向后搜索,相当于此时才将大气球扎爆。虽然看着有些问题,但并不影响根本)

image-20250303192530005

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int findMinArrowShots(int[][] points) {
Arrays.sort(points, (o1, o2) -> {
if (o1[1] != o2[1]) {
return Integer.compare(o1[1], o2[1]);
} else {
return Integer.compare(o1[0], o2[0]);
}
});
int ans = 1;
for (int i = 1, curPoint = points[0][1]; i < points.length; i++) {
if (points[i][0] > curPoint) {
curPoint = points[i][1];
ans++;
}
}
return ans;
}

练习:

种花问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int ans = 0;
for (int i = 0; i < flowerbed.length; i++) {
if (flowerbed[i] == 1) {
continue;
}
if (i - 1 >= 0 && flowerbed[i - 1] == 1) {
continue;
}
if (i + 1 < flowerbed.length && flowerbed[i + 1] == 1) {
continue;
}
flowerbed[i] = 1;
ans++;
}
return ans >= n;
}

划分字母区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public List<Integer> partitionLabels(String s) {
List<Integer> res = new LinkedList<>();
for (int i = 0; i < s.length();) {
int startPoint = i;
for (int curPoint = i; i <= curPoint; i++) {
for (int j = s.length() - 1; j > curPoint; j--) {
if (s.charAt(j) == s.charAt(i)) {
curPoint = j;
break;
}
}
}
res.add(i - startPoint);
}
return res;
}

P1223 排队接水

贪心 + 排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package luogu;

import java.util.Arrays;
import java.util.Scanner;

public class p1223 {
public static class Time implements Comparable<Time> {
int index;
int value;

public Time(int index, int value) {
this.index = index;
this.value = value;
}

@Override
public int compareTo(Time time) {
return this.value - time.value;
}
}

public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
Time[] times = new Time[n];
for (int i = 0; i < n; i++) {
times[i] = new Time(i, in.nextInt());
}
in.close();

Arrays.sort(times);
long ans = 0;
for (int i = 0; i < n; i++) {
System.out.print(Integer.toString(times[i].index + 1) + " ");
ans += times[i].value * (n - i - 1);
}
System.out.println();
System.out.printf("%.2f\n", (double) ans / n);
}

}

[P1090 [NOIP 2004 提高组] 合并果子](P1090 [NOIP 2004 提高组] 合并果子)

贪心 + 堆 + 优先队列

账号登陆

因数据库极易遭受网络攻击,所以不能在数据库中直接存储明文存储。

哈希(Hashing):哈希是单向的,即不可能解密Hash值来获取原始值。

加密(Encryption):加密是双向的,即通过密文解密后可以获取原始值。

为了防止使用密码表或词典进行暴力破解,需要在哈希之前添加盐(Salting)或胡椒(Peppering)再进行哈希。
$$
result = hash(pepper + password + salt)
$$
加盐和胡椒的区别:

  • 所有密码共享pepper,不像盐一样对每个密码都是唯一的。这使得pepper可预测,并可尝试破解密码。pepper的静态特性也“减弱了”哈希抗冲突的能力,而salt则使用独一无二的字符串来扩展长度,提高了哈希抗冲突能力,增加了对哈希函数的输入熵。
  • pepper不存储在数据库中,不像salt一样(但salt也并非总是如此)。
  • pepper并不是使密码破解变得过于困难使攻击变得不可行,这是其它许多密码存储保护措施(如salt)的目标。
  • salt可防止攻击者制作已知密码的彩虹表,但是pepper没有这样的作用。

Bcrypt加密

Bcrypt 是一种基于 Blowfish 密码学算法的密码散列函数。

  • 盐(Salt): bcrypt 在散列过程中使用盐来确保即使相同的密码也会产生不同的散列值,这增加了密码存储的安全性。
  • 工作因子(Work Factor): bcrypt 允许设置一个工作因子,这个因子决定了散列计算的难度。随着计算能力的提升,可以增加工作因子来保持密码散列的安全性。
  • 适应性: bcrypt 可以适应硬件性能的提升,通过增加工作因子来延长散列时间,从而抵抗暴力攻击。

JavaScript

1
npm install bcrypt

Java

Spring Security中包含了BcryptPasswordEncoder

1
2
3
4
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
1
2
@Autowired
private PasswordEncoder passwordEncoder;

HTTPS原理

HTTPS分为两个阶段:握手阶段,传输阶段。

TCP握手 + TLS\SSL握手

网络协议分为五层,首先完成网络层TCP的三次握手,再完成传输层的TLS\SSL的握手,最后到达应用层的HTTPS

network structure

证书生成

生成密钥

openssl生成RSA密钥:

1
openssl genrsa -out prkey.key 2048

或生成RSA加密密钥(使用des3对密钥加密)(可选):

1
openssl genrsa -des3 -out prkey.key 2048

从私钥中导出公钥(可选)

1
openssl rsa -pubout -in prkey.key -out pukey.pem

创建证书签名请求(CSR)

1
openssl req -new -key prkey.key -out cer_request.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=IT/CN=example.com/emailAddress=admin@example.com"

自签名证书

.crt可以换成.pem

1
openssl x509 -req -days 365 -in cer_request.csr -signkey prkey.key -out certificate.crt

CA签署证书(可选)

.crt可以换成.pem

需要先生成 认证中心的 私钥 和 证书,然后对CSR请求生成证书。

1
openssl x509 -req -days 365 -in cer_request.csr -CA ca_cert.crt -CAkey ca_prkey.key -CAcreateserial -out certificate.crt 

查看

1
2
openssl x509 -in certificate.crt -noout -text
openssl req -in cer_request.csr -noout -text

实例

以CA签署证书为例

自签名证书有两种类型:

  • 自签名证书:自签名证书的IssuerSubject是相同的。

  • 私有 CA 签名证书:其中CA的证书是自签名证书,server证书是由CA签发的。

  1. 生成服务器端私钥(server.key)

    1
    genrsa -out server.key 2048
  2. 生成服务端签名证书请求(server.csr)

    1
    openssl req -new -config serverssl.cnf -key server.key -out server.csr
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # serverssl.cnf
    [req]
    default_bits = 2048
    prompt = no
    default_md = sha256
    x509_extensions = v3_req
    distinguished_name = dn

    [dn]
    C = CN
    ST = SD
    L = JINAN
    O = yingzheng
    OU = yingzhengunit
    emailAddress = yingzhengttt@gmail.com
    CN = yingzhengttt.com

    [v3_req]
    subjectAltName = @alt_names

    [alt_names]
    DNS.1 = yingzhengttt.com
    DNS.2 = www.yingzhengttt.com

    其中CN必须与服务器的域名一致。否则浏览器会警告不安全。

  3. 生成CA私钥(ca.key)

    1
    openssl genrsa -out ca.key 2048
  4. 生成CA自签名证书(ca.crt)

    1
    openssl req -new -x509 -days 365 -key ca.key -out ca.crt -subj "/C=CN/ST=SD/L=JINAN/O=yingzheng/OU=yingzhengunit/CN=ROOTcert/emailAddress=ying使用CA证书(ca.crt)对服务端证书请求(server.csr)签名,获取服务端签名(server.crt)zhengttt@gmail.com"
  5. 使用CA证书(ca.crt)对服务端证书请求(server.csr)签名,获取服务端签名(server.crt)

    1
    openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt 

注:本测试在Ubuntu24上的Chrome中,只要是未被公共信任的证书,即使已经加入到系统的信任链中,浏览器也会提示安全风险。但是在Postman中能够正常工作。所以应该是浏览器的安全设置问题。

为了取消报错并在本地使用自签名证书,关闭Chrome的签名认证,在Chrome启动时加入参数 --ignore-certificate-errors。 比如在Ubuntu中,更改/usr/share/applications/google-chrome.desktop,在这里我使用的是canary所以更改/usr/share/applications/google-chrome-canary.desktop

1
Exec=/usr/bin/google-chrome-canary --ignore-certificate-errors %U

SpringBoot启用SSL

server.crtserver.key拷贝到resource目录下。

pom.xml

1
2
3
4
5
6
7
server:
port: 8433
ssl:
certificate: "classpath:server.crt"
certificate-private-key: "classpath:server.key"
http:
port: 8080

创建TomcatConfg.java来创建一个新的Tomcat来接受http请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
public class TomcatConfig {
@Value("${server.http.port}")
private int httpPort;
@Value("${server.port}")
private int httpsPort;
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
};
factory.addAdditionalTomcatConnectors(createTomcatConnector());
return factory;
}

@Bean
protected Connector createTomcatConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(httpPort);
connector.setSecure(false);
connector.setRedirectPort(httpsPort);
return connector;

}
}

并在spring security中配置之允许使用https协议,并将http协议跳转到https协议端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.portMapper(portMapper -> portMapper.http(8080).mapsTo(8443))
.requiresChannel(channel -> channel.anyRequest().requiresSecure())
...;
return http.build();
}

}

Springboot 添加信任证书

如果只想添加信任证书能够访问其他https服务,而不是自身启动https,可以只添加证书到信任链中。

方法1. 将证书添加到Java信任库

首先获取需要信任的证书ca.crt

ca.crt导入到Java信任库中

1
keytool -import -trustcacerts -keystore $JAVA_HOME/lib/security/cacerts -noprompt -alias my-cert -file ca.crt

方法2. 将证书添加到Springboot中

该方法并没有成功

创建JKS信任库

1
keytool -import -trustcacerts -keystore server.jks -storepass 1234 -alias my-cert -file ca.crt

server.jks 添加到resource目录下

application.yml

1
2
3
4
5
6
7
ssl:
key-store: ""
key-store-password: ""
key-store-type: ""
trust-store: classpath:server.jks
trust-store-password: 1234
trust-store-type: JKS

问题:即使将key-store设置为空,但启动的时候springboot仍然去/home/amber/.keystore (No such file or directory)去拿key-store。一直解决不了。如果设置trust-store,springboot就默认启动ssl也会去找key-store,即使设置为空也还是去找。

Nginx 启用 SSL

配置Nginx

1
sudo vim /etc/nginx/nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server {
listen 8082 ssl;
server_name localhost;

ssl_certificate /home/amber/Documents/openssl/certificate.crt;
ssl_certificate_key /home/amber/Documents/openssl/prkey.key;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

location /ws/ {
access_log /home/amber/Downloads/websocket.log;
proxy_pass http://localhost:8080/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_read_timeout 86400;
}
}

后端配置了两个websocket服务ws://localhost:8080/echows://localhost:8080/chat

Nginx监听8082端点,并设置SSL服务,将/ws/*转发给后端localhost:8080/*对应的服务。即访问wss://localhost:8082/ws/echo会解析SSL并转发给ws://localhost:8080/echo

前后端交互使用Javascript。目前底层原生工具有fetchXMLHttpRequest。开发常用的axios是对fetch的封装。这两种通信方式都是HTTP/HTTPS协议,即无状态协议。

跨域问题:

Fetch 如果后端不支持CORS,前端是无法实现跨域访问的。

1
2
3
4
5
6
7
8
9
10
11
12
const headers = new Headers();
headers.set("Content-Type", "application/json");
const request = new Request(process.env.NEXT_PUBLIC_LOGIN_API!, {
method: "POST",
mode: "cors",
headers: headers,
});
const response = await fetch(request);
if (!response.ok) {
throw new Error(`Response status ${response.status}`);
}
const json = await response.json();

Git 常用命令

Git分为工作区,版本库(暂存区和提交历史)

工作区即项目目录,版本库为其中的.git隐藏文件夹,其中index即为暂存区。

1
2
3
git add <filename>
git commit -m "message"
git commit -a -m "message"
1
2
3
4
git switch <branchname>
git swtich -c <branchname>
git checkout <branchname>
git checkout -b <branchname>
1
2
3
git branch
git branch -d <branchname>
git branch -D <branchname> #force delete
1
2
3
git stash
git stash pop
git stash supply <stashid>
1
2
3
4
git cherry-pick <commithash>
git cherry-pick <startcomit> <endcommit> #(startcommit, endcommit]
git cherry-pick --continue #deal with conflict
git cherry-pick --abort #abort cherry pick
1
git log --graph --pretty=oneline --abbrev-commit

Switch

场景 命令 说明
切换已有本地分支 git switch dev 快速切换
创建并切换新分支 git switch -c dev 不关联远程
基于远程创建并跟踪 git switch --track -c dev origin/dev 推荐用法
基于 tag 创建新分支 git switch -c dev v1.0 基于 tag
切换到 tag git switch v1.0 游离 HEAD
切换到 commit git switch a1b2c3d 游离 HEAD
强制切换丢弃修改 git switch -f dev 小心使用
游离 HEAD(只查看) git switch --detach origin/dev 只看不提交

Git在切换分支时,如果工作区或暂存区存在数据并没有提交,Git会尝试将这些数据(未追踪的数据,修改但没加入暂存区的数据,加入暂存区的数据)转移到新分支的工作区和暂存区,如果合并过程中出现冲突,那么Git会阻止分支切换。

解决冲突的方案:

  • 在切换分支之前进行Commit操作,将所有工作区,暂存区的数据都提交,放入到版本库中
  • 在切换分支之前使用Stash将工作区,暂存区的数据都保留镜像,等到切换回该分支后再执行pop将工作区,暂存区恢复。
  • 直接放弃在工作区,暂存区的修改。

注意:一般来说,在切换分支前需要将工作区、暂存区清空,防止将一些不必要的修改带入到其他分支。

工作区中包含了未被追踪的数据加入暂存区后又被修改的数据

暂存区中包含了上一次add时数据的状态

Restore

命令 作用
git restore <file> 恢复指定文件到最新提交版本,覆盖工作区的改动(未提交的更改会丢失)。
git restore . 恢复所有文件,丢弃全部工作区未提交的更改。
git restore --staged <file> 取消暂存区的指定文件,保留工作区改动。
git restore --staged . 取消所有已暂存的文件,保留工作区改动。
git restore --staged --worktree <file> 同时恢复暂存区和工作区,彻底还原文件到最近一次提交版本。
git restore --source=<branch> --staged --worktree <file> 从指定提交恢复文件,同时覆盖工作区和暂存区。
需求 命令
恢复工作区到仓库版本 git restore <file>
恢复工作区到暂存区版本 git restore --source=: <file>
恢复暂存区到仓库版本 git restore --staged <file>

Stash

功能 命令
保存修改 git stash
保存修改(含未跟踪) git stash -u
保存所有 git stash -a
查看列表 git stash list
查看修改 git stash show -p stash@{0}
应用最新 stash (但不删除) git stash apply
应用并删除 git stash pop
删除指定 stash git stash drop stash@{0}
清空 stash git stash clear
交互式 stash git stash push -p
新建分支恢复 stash git stash branch new-branch stash@{0}

Merge / Rebase

image-20250629210400266

Reset / Revert

操作 修改历史 生成新提交 是否安全 用途
git reset --soft <commit> ✅ 是 ❌ 否 ❌ 否 回退到目标 commit,将当前commit保留到暂存区
git reset --mixed <commit> ✅ 是 ❌ 否 ❌ 否 回退到目标 commit,将当前commit保留到工作区
git reset --hard <commit> ✅ 是 ❌ 否 ❌ 否 回退到目标 commit,丢弃所有修改
git revert ❌ 否 ✅ 是 ✅ 是 撤销当前 commit,生成一个新的节点,协作开发,不改历史
命令 HEAD 指针移动 暂存区变化 工作区变化 场景总结
git reset --soft HEAD^ ✅ 回退 保持回退前commit的内容 ❌ 保留(工作区内容不变,保持回退前commit的内容) 回退 commit,修改回到暂存区,适合继续提交
git reset --mixed HEAD^ ✅ 回退 更改至目标commit的内容 ❌ 保留(工作区内容不变,保持回退前commit的内容) 回退 commit,修改回到工作区,适合继续编辑
git reset --hard HEAD^ ✅ 回退 更改至目标commit的内容 ✅ 恢复为目标commit的状态 回退 commit,直接丢弃修改,彻底干净

Push

添加远程分支

功能 命令
添加远程 git remote add origin <url>
查看远程 git remote -v
修改远程地址 git remote set-url origin <new-url>
删除远程 git remote remove origin

push本地分支到远程分支并关联在一起

场景 是否需要手动指定
第一次推送主分支 需要执行 git push -u origin main
后续推送主分支 直接 git push 即可
新建其他分支第一次推送 需要执行 git push -u origin 分支名
新建其他分支后续推送 直接 git push 即可

Fetch / Pull

git fetch + git merge = git pull

Spring 持久化框架

Spring提供了JDBC(Java Database Connectivity)JPA (Java Persistence API)MyBatis

JDBC(Java Database Connectivity) 是Java的标准数据库访问技术。它直接与数据库交互,允许Java应用程序执行SQL语句并与数据库进行通信。

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>3.4.2</version>
</dependency>

JPA 是一个Java的持久化API,用于管理Java对象与数据库之间的映射。它提供了对象关系映射(ORM)的功能,允许开发者以面向对象的方式处理数据。

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.4.2</version>
</dependency>

MyBatis 是一个支持普通SQL查询、存储过程和高级映射的持久层框架。它避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatus是半自动ORM:MyBatis不像JPA那样完全抽象掉SQL,它允许开发者写SQL语句,同时提供了映射文件来定义SQL语句与Java对象的映射关系。

就目前版本的mybatis中包含了spring-boot-starter-jdbc,直接使用JDBC的Datasource和数据库连接池。

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>

SpringBoot 数据库连接池

Spring IOC 循环依赖

Spring Session

Spring Session 是 Spring 框架的一个项目,旨在提供会话管理的解决方案。它可以与各种后端存储(如内存、数据库、Redis 等)集成。

  • SecurityContextHolderFilter中的SecurityContextRepository,存储用户信息上下文,其中一种实现方式就是HttpSessionSecurityContextRepository,而且该方式是默认的实现方式。
  • AbstractAuthenticationProcessingFilter中,当用户登录成功,也是会调用this.securityContextRepository.saveContext(context, request, response);存储上下文到session中。
0%