protobuf的安装和基本使用

一、安装protobuf

下载连接:protobuf-cpp-3.21.12

1.1、ubuntu安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tar -zxvf protobuf-cpp-3.21.12.tar.gz

cd protobuf-cpp-3.21.12

sudo apt install automake libtool curl make g++ unzip

./autogen.sh

./configure

make -j$(nproc)

sudo make install

sudo Idconfig

2.1、windows安装

使用Clion打开protobuf项目,根据需要选择编译套件

PixPin_2025-03-10_12-21-28

打开根目录下的CMakeLists.txt文件

  • 修改protobuf_BUILD_TESTS为OFF,取消编译测试代码
  • 添加set(BUILD_SHARED_LIBS ON),用于生成动态库文件

image-20250308170612706

然后点击编译,然后就会生成以下几个文件,将文件拷贝出来

image-20250310015149675

自己找一个存放protobuf库的目录,用于存放这几个文件

image-20250308171436674

bin:将三个dll文件和exe文件都放在这个目录

lib:将另外三个dll.a文件放在这个目录

include:可以直接拷贝源代码中的src目录下的google目录,但是源代码目录下会有源文件,如果有洁癖,可以在linux目录下拷贝打包一份(默认你已经在ubuntu上手动编译安装protobuf),执行以下命令

1
2
cd /usr/local/include
sudo tar zcvf google.tar.gz google/

将压缩包传输到include目录解压即可
最后,将lib和bin目录添加到环境变量

二、在cmake中的使用

3.1 windows

1
2
3
4
5
6
#如果使用MSVC编译套件,需要添加下面这个,告诉 Protocol Buffers 库以 DLL 模式进行编译和链接
#add_compile_definitions(PROTOBUF_USE_DLLS)
set(PROTOBUF_PATH "D:/protobuf-cpp-3.21.12")
include_directories(${PROTOBUF_PATH}/include)
link_directories(${PROTOBUF_PATH}/lib)
target_link_libraries(${PROJECT_NAME} protobufd)

3.2 ubuntu

1
target_link_libraries(${PROJECT_NAME} protobuf)

三、4、基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
syntax = "proto3";

package fixbug;

message LoginRequest
{
string name = 1;
string pwd = 2;
}

message LoginResponse
{
int32 errCode = 1;
string errMsg = 2;
bool succuss = 3;
}

这里的每一个message都对应着一个类,package为命名空间,相当于C++中的namespace,syntax为protobuf的版本号

其中,string常用bytes替换,即

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
syntax = "proto3";

package fixbug;

option cc_generic_services = true;//设置可以生成服务类

message ResultCode
{
int32 errCode = 1;
bytes errMsg = 2;
}

message LoginRequest
{
bytes name = 1;
bytes pwd = 2;
}

message LoginResponse
{
ResultCode result = 1;
bool succuss = 2;
}

message GetFriendListRequest
{
uint32 userId = 1;
}

message User
{
bytes name = 1;
uint32 age = 2;
enum Sex
{
MAN = 0;
WOMAN = 1;
}
Sex sex = 3;
}

message GetFriendListResponse
{
ResultCode result = 1;
repeated User friend_list = 2; //列表类型
}

这样可以直接使用字节存储,不需要从string转为bytes,提高效率,使用示例

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
#include "test.pb.h"
#include <iostream>

using namespace fixbug;

int main()
{

// 如果类里面包含其他类,需要使用mutable+变量名才能修改,
// 因为变量名返回的是引用,无法修改
// LoginResponse response;
// ResultCode *rc = response.mutable_result();
// rc->set_errcode(1);
// rc->set_errmsg("调用失败");

//如果修改的是列表,需要调用add+变量名才能修改
GetFriendListResponse response;
ResultCode* rc = response.mutable_result();
rc->set_errcode(0);

//获取修改地址
User* user1 = response.add_friend_list();
user1->set_name("zhangsan");
user1->set_age(20);
user1->set_sex(User::MAN);

User* user2 = response.add_friend_list();
user2->set_name("lisi");
user2->set_age(21);
user2->set_sex(User::WOMAN);

std::cout << "resonse friend_list size : " << response.friend_list_size() << std::endl;

//获取数据
for(int i = 0; i < response.friend_list_size(); ++i)
{
std::cout << "name : " << response.friend_list(i).name() << '\n'
<< "age : " << response.friend_list(i).age() << '\n'
<< "sex : " << response.friend_list(i).sex() << std::endl;
}
}

int main1()
{
std::cout << "hello world" << std::endl;
// 如果是一个普通的对象,直接使用set函数即可修改
LoginRequest request;
request.set_name("jianzhe");
request.set_pwd("123456");
std::string msg;

if (request.SerializeToString(&msg))
{
std::cout << "msg : " << msg << std::endl;
}

LoginRequest parseRequest;
if (parseRequest.ParseFromString(msg))
{
std::cout << "name : " << parseRequest.name() << std::endl;
std::cout << "pwd : " << parseRequest.pwd() << std::endl;
}

return 0;
}

相关介绍

返回值:

  1. clear_result()
  • 清除 result 字段的值
  • 将其重置为默认值
  1. result() const
  • 获取 result 字段的常量引用
  • 只读访问,不能修改字段值
  1. release_result()
  • 释放 result 字段的所有权
  • 返回指向该字段的指针
  • 调用后消息不再拥有该字段
  1. mutable_result()
  • 获取 result 字段的可修改指针
  • 允许修改字段值
  • 如果字段不存在会创建默认值
  1. set_allocated_result()
  • 设置 result 字段的新值
  • 接管传入指针的所有权
  • 释放原有的 result 字段

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LoginResponse response;

// 获取只读访问
const ResultCode& code = response.result();

// 获取可修改访问
ResultCode* mutable_code = response.mutable_result();
mutable_code->set_errcode(1);

// 释放所有权
ResultCode* released = response.release_result();

// 设置新值
ResultCode* new_code = new ResultCode();
response.set_allocated_result(new_code);

// 清除值
response.clear_result();

网络通信中的处理粘包问题

一、粘包问题基本概念

网络粘包是指在TCP流式传输中,由于TCP协议的特性,多个数据包可能被合并成一个数据包接收(粘包),或者一个数据包被分割成多个数据包接收(半包)。这会导致接收端无法正确识别消息边界,从而造成数据解析错误。

主要表现形式:

  1. 粘包问题:多个数据包被合并成一个数据包接收
  2. 拆包问题:单个应用层报文被分割成多个TCP段接收

示例情况:

发送端: [Packet1] [Packet2] [Packet3]
接收端可能收到:
情况1: [Packet1] [Packet2] [Packet3](理想情况)
情况2: [Packet1] [Packet2部分数据](半包)
情况3: [Packet1部分] [Packet2] [Packet3部分](粘包+半包)

二、长度前缀(Length-Prefix)协议

最常用的解决方案是采用”长度+数据”的格式进行通信,即在每个消息前添加一个固定长度的字段,用于表示后续数据的长度。

1
[4字节长度字段(网络字节序)][实际数据(长度由头部指定)]

长度字段使用 uint32_t 大端序(网络字节序)

三、发送端处理

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
#include <arpa/inet.h>  // htonl/ntohl
#include <vector>

void send_message(int sockfd, const std::vector<char>& data) {
// 添加长度校验
// if (data.size() > std::numeric_limits<uint32_t>::max()) {
// throw std::invalid_argument("Data size exceeds 4GB");
// }
// 构造协议头部,
uint32_t data_length = static_cast<uint32_t>(data.size());
uint32_t net_length = htonl(data_length);

// 构造发送缓冲区(二进制安全)
std::vector<char> buffer(sizeof(net_length) + data.size());
memcpy(buffer.data(), &net_length, sizeof(net_length));
memcpy(buffer.data() + sizeof(net_length), data.data(), data.size());

// 完整发送(处理部分发送情况)
size_t total_sent = 0;
while (total_sent < buffer.size()) {
ssize_t sent = send(sockfd, buffer.data() + total_sent,
buffer.size() - total_sent, 0);
if (sent == -1) {
if (errno == EINTR) continue; // 处理系统中断
throw std::runtime_error("send failed: " + std::string(strerror(errno)));
}
total_sent += sent;
}
}

四、接收端处理

4.1 基本接收方法

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
// 首先接收4字节的长度字段
uint32_t network_length;
size_t total_read = 0;
while (total_read < sizeof(uint32_t)) {
ssize_t n = recv(socket_fd, (char*)&network_length + total_read,
sizeof(uint32_t) - total_read, 0);
if (n <= 0) { /* 处理错误 */ }
total_read += n;
}

// 将网络字节序转换为主机字节序
uint32_t length = ntohl(network_length);

// 根据长度字段分配缓冲区并接收实际消息
std::vector<char> buffer(length);
size_t total_read_body = 0;
while (total_read_body < length) {
ssize_t n = recv(socket_fd, buffer.data() + total_read_body,
length - total_read_body, 0);
if (n == 0) {
throw std::runtime_error("Connection closed by peer");
} else if (n < 0) {
if (errno == EINTR) continue;
throw std::runtime_error("recv error: " + std::string(strerror(errno)));
}

total_read_body += n;
}

// 处理接收到的消息
process_message(buffer, length);

4.2 处理粘包和半包的完整状态机实现

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
#include <sys/socket.h>
#include <stdexcept>

class MessageParser {
public:
static constexpr uint32_t MAX_MSG_SIZE = 16 * 1024 * 1024; // 16MB

enum class ParseState {
ReadingHeader, // 正在读取长度头
ReadingBody // 正在读取消息体
};

//不使用std::vector<std::string>的原因:
//std::string 内部以 \0 作为终止符,若消息中包含二进制数据(如图片、加密内容等)存在 0x00 字节,会导致数据被意外截断:
//std::vector<char> 直接存储原始字节流,无任何隐式转换,保证数据完整性
std::vector<std::vector<char>> parse(const char* input, size_t len) {
std::vector<std::vector<char>> complete_messages;
size_t processed = 0;

while (processed < len) {
switch (current_state_) {
case ParseState::ReadingHeader: {
size_t remain = HEADER_SIZE - header_bytes_received_;
//len - processed 表示当前输入数据中还未处理的字节数。len 是传入的数据总长度,processed 是已经处理的字节数。
//std::min() 函数取这两个值中的较小值,确保:
//如果header_remaining小,确保不会读取超过头部所需的字节数
//如果length - processed小,确保不会尝试读取超出当前可用数据范围的字节
size_t to_copy = std::min(remain, len - processed);

// 拷贝到头部缓冲区
memcpy(header_buf_ + header_bytes_received_, input + processed, to_copy);

header_bytes_received_ += to_copy;
processed += to_copy;

// 头部接收完成
// header_bytes_received_ < HEADER_SIZE,会直接退出switch
// 在处理下一个数据包时,由于header_buf_没有清空和current_state_仍是ReadingHeader
// header_bytes_received_也不变,可以继续从 header_buf_ 的位置追加数据
// 保证了正确的处理剩余的数据包,直到满足header_bytes_received_ == HEADER_SIZE
// 然后重置状态机相关信息,下面的ParseState::ReadingBody同理
if (header_bytes_received_ == HEADER_SIZE) {
// 转换网络字节序
uint32_t net_length;
memcpy(&net_length, header_buf_, sizeof(net_length));
current_body_length_ = ntohl(net_length);

// 安全检查
if (current_body_length_ > MAX_MSG_SIZE) {
throw std::runtime_error("Message size exceeds limit");
}

// 准备接收消息体
current_body_.resize(current_body_length_);
body_bytes_received_ = 0;
current_state_ = ParseState::ReadingBody;
}
break;
}

case ParseState::ReadingBody: {
size_t remain = current_body_length_ - body_bytes_received_;
size_t to_copy = std::min(remain, len - processed);

//assert(current_body_.size() >= body_bytes_received_ + to_copy);
memcpy(current_body_.data() + body_bytes_received_,
input + processed, to_copy);

body_bytes_received_ += to_copy;
processed += to_copy;

// 消息体接收完成
if (body_bytes_received_ == current_body_length_) {
complete_messages.push_back(std::move(current_body_));
reset();
}
break;
}
}
}
return complete_messages;
}

private:
void reset() {
current_state_ = ParseState::ReadingHeader;
header_bytes_received_ = 0;
body_bytes_received_ = 0;
current_body_.clear();
}

static constexpr size_t HEADER_SIZE = sizeof(uint32_t);

ParseState current_state_ = ParseState::ReadingHeader;
char header_buf_[HEADER_SIZE] = {0};
size_t header_bytes_received_ = 0;
uint32_t current_body_length_ = 0;
size_t body_bytes_received_ = 0;
std::vector<char> current_body_;
};

4.3 完整接收流程

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
void message_loop(int sockfd) {
MessageParser parser;
std::vector<char> recv_buf(4096); // 4KB接收缓冲区

while (true) {
ssize_t n = recv(sockfd, recv_buf.data(), recv_buf.size(), 0);
if (n == 0) {
// 连接正常关闭
break;
} else if (n < 0) {
if (errno == EINTR) continue; // 处理系统中断
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 非阻塞模式下无数据可用,应通过select/poll/epoll等待
// 此处简单重试仅用于演示目的
usleep(1000);
continue;
}
throw std::runtime_error("recv error: " + std::string(strerror(errno)));
}

// 解析消息
auto messages = parser.parse(recv_buf.data(), n);

// 处理完整消息
for (auto& msg : messages) {
process_message(msg.data(), msg.size());
}
}
}

五、关键处理要点

  1. 状态机处理:使用状态机区分正在读取头部还是读取消息体
  2. 缓冲区管理:动态调整缓冲区大小,根据需要存储部分接收的数据
  3. 字节累积:持续累积接收的字节,直到获取完整的消息
  4. 多消息处理:一次接收可能包含多个完整消息,需要全部解析出来
  5. 安全检查:验证消息长度的合理性,防止恶意攻击
  6. 字节序问题:在不同架构的计算机之间通信时,必须使用网络字节序(大端序)。使用htonl()将主机字节序转换为网络字节序,使用ntohl()将网络字节序转换为主机字节序
  7. 长度字段大小:通常使用4字节(uint32_t)来表示长度,这足以表示最大4GB的消息
  8. 完整接收:TCP不保证一次recv调用能接收到完整数据,可能需要循环接收直到获取所需的全部字节

六、总结

TCP流式协议中处理粘包和半包问题的核心是:

  1. 使用固定长度的头部标识消息长度
  2. 使用状态机和缓冲区管理接收的数据
  3. 阻塞等待直到接收到完整数据
  4. 单次接收可能同时包含多个完整消息和不完整消息,需要综合处理
  5. 不同的实现方式(memcpy、string操作)各有优缺点,可根据具体需求选择

FRP内网穿透教程

一、准备工作

  1. 正常运行的公网IP服务器
  2. 域名(可选)

二、配置内网穿透

1、公网云服务器

切换到root用户执行安装kejilion大佬的脚本文件

1
bash <(curl -sL kejilion.sh)

脚本地址:https://github.com/kejilion/sh

  • 选择11进入应用市场

image-20250306201129922

  • 选择55进入FRP内网穿透(服务端)

image-20250306201417856

选择1安装FRP服务端,保存客户端部署时需要用的参数

image-20250306201745610

2、本地(WSL)

完成上述服务器端配置后,切换到本地,这里使用WSL为例

同样切换到root用户执行安装kejilion大佬的脚本文件

1
bash <(curl -sL kejilion.sh)
  • 选择11进入脚本市场,选择56进入FRP内网穿透(客户端)

image-20250306202352350

  • 然后选择1安装FRP客户端,根据提示填写信息,等待安装完成

image-20250306202646846

  • 外网对接IP就是公网云服务器的IP
  • token就是服务端生成的token
  • 选择4添加一个对外服务

image-20250306203429499

  1. 服务名称:随便填
  2. 转发类型: 直接回车
  3. 内网IP: 直接回车
  4. 内网端口: 本地服务运行端口
  5. 外网端口:外网服务器通过这个端口访问本地服务,可以相同,也可以不同,如果对外服务的是MySQL,且公网服务器也有MySQL,可以填写3307防止冲突

完成后会发现已经完成了一个映射,内网穿透已经可以使用,通过公网IP+端口即可访问本地服务

  • 客户端

image-20250306203949531

  • 服务端,没有显示选择00刷新状态

image-20250306205257292

三、进阶

1、添加自定义域名

在服务端选择5,添加一个内网服务域名访问

image-20250306205713920

注意,域名记得解析并代理服务器的IP地址

添加以后,我们就可以通过域名访问本地服务了

image-20250306205955812

然后选择8阻止通过IP+端口的方式访问

image-20250306212117235

注意,服务端删除内网穿透时,记得先选择6删除域名访问和选择7允许IP+端口访问,防止下次使用时发生问题,最后,如果之前删除过服务,别忘记修改客户端的token!

完结撒花

更换 Typecho 站点域名

一、修改数据库

1
2
3
4
5
6
7
8
9
# 登录数据库
mysql -u root -p

# 选择数据库
use typecho;

# 更新网站地址
UPDATE typecho_options SET siteUrl = 'https://example.com'
UPDATE typecho_users SET url = 'https://example.com'

二、修改 Nginx 配置文件

1
2
nano /etc/nginx/sites-available/typecho
nano /etc/nginx/nginx.conf

注意一下几点:

  1. server_name 改为新域名
  2. typecho根路径
  3. SSL证书路径

三、检查 config.inc.php 文件

1
nano /var/www/html/config.inc.php

确保没有硬编码的旧域名。

四、重启 Nginx

1
2
nginx -t
systemctl restart nginx

五、刷新网站缓存

1
rm -rf /var/www/html/usr/cache/*

最后,别忘了更新域名的 DNS 解析记录,将新域名指向你的服务器 IP。

参考:更换 Typecho 站点域名

[转载] Typecho博客迁移完整指南

From: Rational
Author: Rational
原文链接:Typecho博客迁移完整指南

一、数据备份

1. 数据库备份

1
mysqldump -u root -p typecho > typecho_backup.sql
  • mysqldump 只会导出数据库的结构和数据内容,不会包含数据库的用户名和密码信息。也就是说,备份文件 typecho_backup.sql 中不会保存与数据库连接相关的用户名和密码。所以后文需要CREATE USER

2. 网站文件备份

1
2
cd /var/www/html
tar -zcvf typecho_files.tar.gz *

二、数据迁移

1. 传输文件(二选一)

1
2
3
4
5
6
7
8
9
10
11
# 方式一:直接从源服务器到目标服务器
scp -r /var/www/html/* username@new_server:/var/www/html/
scp typecho_backup.sql username@new_server:/root/

# 方式二:通过本地中转
# 先下载到本地
scp username@old_server:/var/www/html/typecho_files.tar.gz ./
scp username@old_server:/path/to/typecho_backup.sql ./
# 再上传到新服务器
scp typecho_files.tar.gz username@new_server:/var/www/html/
scp typecho_backup.sql username@new_server:/root/

2. 恢复数据

2.1 文件恢复

1
2
3
4
5
cd /var/www/html
tar -zxvf typecho_files.tar.gz
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
chmod -R 777 /var/www/html/usr/uploads

2.2 数据库恢复

登录MySQL-创建数据库-创建用户密码-赋予权限-刷新-退出

1
2
3
4
5
6
7
mysql -u root -p

CREATE DATABASE typecho;
CREATE USER 'typecho'@'localhost' IDENTIFIED BY '密码';
GRANT ALL PRIVILEGES ON typecho.* TO 'typecho'@'localhost';
FLUSH PRIVILEGES;
EXIT;

创建的数据库名和用户名密码可以为新的。例如数据库名改为typechho1,那么下一步的typecho需改为typecho1。同时config.inc.php中也需要更改相应的参数。

  • 导入数据

    1
    mysql -u root -p typecho < /root/typecho_backup.sql

三、配置调整

1. 修改配置文件

1
nano /var/www/html/config.inc.php

需检查:

  • 数据库连接信息
  • 网站URL
  • 网站路径

2. 清理缓存

1
rm -rf /var/www/html/usr/cache/*

3. 重启服务

1
2
systemctl restart nginx
systemctl restart php7.4-fpm

php7.4-fpm版本需要自行检查

四、故障排查

1. 查看日志

1
2
3
4
5
# Nginx日志
tail -f /var/log/nginx/error.log

# PHP日志
tail -f /var/log/php7.4-fpm.log

2. 常见问题解决

  • 数据库连接错误:检查 config.inc.php 配置

  • 图片显示异常:检查 uploads 目录权限

  • 后台登录问题:
    查看数据库中的用户表:

    1
    SELECT * FROM typecho_users;

    修改管理员url:

    1
    UPDATE typecho_users SET url = 'https://新域名' WHERE uid = 1;

    重置管理员密码:

    1
    UPDATE typecho_users SET password = MD5('新密码') WHERE uid = 1;

    修改管理员用户名:

    1
    UPDATE typecho_users SET name = '新管理员用户名' WHERE name = 'admin';

五、注意事项

  1. 迁移前停用插件
  2. 确保PHP版本兼容
  3. 保留原站备份
  4. 测试所有功能:
    • 前台访问
    • 后台登录
    • 文章显示
    • 图片加载
    • 评论功能

配置MySQL或MariaDB远程登录

一、放行端口

1
2
sudo ufw allow 3306
sudo ufw reload

如果是云服务器,需要在控制台放行端口

二、修改配置文件

1、MySQL配置文件路径(ubuntu)

1
2
nano /etc/mysql/mysql.conf.d/mysqld.cnf
# 找到bind-address = 127.0.0.1并注释

2、 MariaDB配置文件路径(centos)

1
2
3
4
nano /etc/my.cnf
# 在配置文件中找到[mysqld]部分,并添加或修改成以下行:
# [mysqld]
# bind-address=0.0.0.0

配置路径可能不同

三、重启MySQL

1
sudo systemctl restart mysql

四、添加远程登录用户

1、检查用户

1
SELECT host FROM mysql.user WHERE User = 'root';

如果只看到带有 localhost127.0.0.1 的结果,这将无法从外部连接。如果您看到其他 IP 地址,但没有您连接的地址,这也是无法连接的。

2、添加用户

可以允许指定ip地址的用户以root用户身份登录

1
2
CREATE USER 'root'@'ip_address' IDENTIFIED BY 'some_pass';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'ip_address';

允许任何IP地址的用户以root用户身份登录

1
2
CREATE USER 'root'@'%' IDENTIFIED BY 'some_pass';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';

3、 刷新全新

1
FLUSH PRIVILEGES;

ubuntu部署typecho博客系统

在配置过程中,我遇到了一直出现nginx欢迎页的bug,后面发现是us.kg域名的问题,在我换成blog域名后成功了,问题出现的原因可能是前段时间us.kg崩了,于是我从cloudflare中删除了该域名,后面添加回来,可能是没有清除以前的dns记录导致的?我没有验证,大家可以自行尝试。

1. 更新系统

1
2
3
sudo -i
apt update
apt upgrade -y

2. 安装 Nginx

1
2
3
apt install nginx -y
systemctl start nginx
systemctl enable nginx

3. 安装MySQL(MariaDB)

1
2
3
apt install mariadb-server -y
systemctl start mariadb
systemctl enable mariadb

配置数据库

1
mysql_secure_installation

在执行 mysql_secure_installation 时,会问你几个问题:

  • Enter current password for root: 直接按回车
  • Set root password? [Y/n]:输入 Y,然后设置 root 密码
  • Remove anonymous users? [Y/n]:输入 Y
  • Disallow root login remotely? [Y/n]:输入 Y
  • Remove test database? [Y/n]:输入 Y
  • Reload privilege tables now? [Y/n]:输入 Y

4. 为 Typecho 创建数据库和用户

1
mysql -u root -p
  • 使用 root 身份连接 mysql -u 表示指定用户名 -p 表示需要输入密码
1
2
3
4
5
CREATE DATABASE typecho;
CREATE USER 'typecho'@'localhost' IDENTIFIED BY '设置一个密码';
GRANT ALL PRIVILEGES ON typecho.* TO 'typecho'@'localhost';
FLUSH PRIVILEGES;
exit;
  • 创建名为 typecho 的数据库,并创建一个 typecho 用户,指定其此用户仅允许本地访问,且设置一个密码,然后授予该用户对 typecho 数据库的所有权限。最后刷新一下。

5.安装 PHP 及必要扩展

1
2
3
apt install php-fpm php-mysql php-gd php-curl php-mbstring php-xml php-zip -y
systemctl start php8.3-fpm
systemctl enable php8.3-fpm
  • 注意php-fpm版本,可用通过以下命令查看
1
2
3
4
dpkg -l | grep php-fpm

#输出:其中,8.3就是版本
#ii php-fpm 2:8.3+93ubuntu2 all server-side, HTML-embedded

6. 下载和配置 Typecho

1
2
3
4
5
6
7
8
9
10
cd /var/www/html
rm /var/www/html/index.nginx-debian.html #nginx欢迎页
mkdir typecho
cd typecho
wget https://github.com/typecho/typecho/releases/latest/download/typecho.zip
apt install unzip -y
unzip typecho.zip
chown -R www-data:www-data /var/www/html/typecho
chmod -R 755 /var/www/html/typecho
chmod -R 777 /var/www/html/typecho/usr/uploads

7. 配置 Nginx

1
vim /etc/nginx/sites-available/typecho.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
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
# HTTP 80 -> HTTPS 301重定向
server {
listen 80; # IPv4监听
server_name example.com;

# 强制HTTPS
if ($http_x_forwarded_proto != 'https'){
return 301 https://$server_name$request_uri; # 只在用户原始请求是HTTP时重定向;
}
}

# HTTPS核心服务配置
server {
listen 443 ssl; # SSL端口监听
server_name example.com;

# SSL证书路径 (必须绝对路径!)
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;

# SSL强化协议
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;

# 根目录设置
root /var/www/html/typecho; # Typecho程序路径
index index.php;

# 全局安全头(可选)
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin";

# 主请求处理
location / {
try_files $uri $uri/ /index.php?$args;
}

# PHP处理规则
location ~ \.php$ {
include fastcgi_params;
fastcgi_param HTTPS on;
fastcgi_pass unix:/run/php/php8.3-fpm.sock; # 匹配实际PHP版本
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

# 拒绝访问隐藏文件
location ~ /\.(?!well-known) {
deny all;
}

# 自定义错误页
# error_page 404 /404.html;
# error_page 500 502 503 504 /50x.html;

#location = /50x.html {
# internal; # 禁止外部直接访问
# root /var/www/html/error_pages;
#}
}
  • 如果你的域名挂在cloudflare,你可以通过进入域名,SSL/TLS—>源服务器—>创建证书

修改nginx.conf,记得备份,将原有的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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
user www-data;                     # Ubuntu系统专用用户
worker_processes auto; # 自动根据CPU核心数优化
worker_rlimit_nofile 65535; # 提升文件描述符限制
pid /run/nginx.pid;

# 错误日志配置
error_log /var/log/nginx/error.log notice;

# 事件模块
events {
use epoll; # Linux高性能模式
worker_connections 8192; # 高并发场景建议值
multi_accept on; # 允许同时接受新连接
}

# HTTP核心配置
http {
include /etc/nginx/mime.types; # ▲ 唯一引入点!
default_type application/octet-stream; # ▼ 全局默认类型

# 日志格式
log_format combinedio '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_length $request_time $upstream_response_time';

access_log /var/log/nginx/access.log combinedio;

# 性能优化参数
sendfile on; # ✔️ 唯一全局定义
tcp_nopush on; # 提升TCP效率
tcp_nodelay on; # 禁用Nagle算法
keepalive_timeout 65; # 合理的保持连接时间
client_body_timeout 12; # 统一客户端超时
client_header_timeout 12;

# 安全增强
server_tokens off; # 隐藏Nginx版本号

# 压缩配置
gzip on; # ▼ 全局压缩开关
gzip_types text/css application/json application/javascript;

# 包含动态配置
include /etc/nginx/conf.d/*.conf; # 基础配置片段
include /etc/nginx/sites-enabled/typecho.conf; # 虚拟主机配置
}

验证配置:

1
2
3
4
nginx -t
#输出
#nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
#nginx: configuration file /etc/nginx/nginx.conf test is successful

8. 启用站点配置

1
2
3
4
ln -sf /etc/nginx/sites-available/typecho.conf /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
nginx -t
systemctl restart nginx

9. 访问网站完成安装

现在你可以通过浏览器访问你的域名或服务器 IP,会看到 Typecho 的安装界面。按照以下步骤完成安装:

PixPin_2025-02-26_23-42-18

  • 选择 “开始安装”
  • 数据库适配器选择 “MySQL”
  • 数据库地址填写 “localhost”
  • 数据库端口保持默认 “3306”
  • 数据库用户名填写 “typecho”
  • 数据库密码填写你之前设置的密码
  • 数据库名填写 “typecho”
  • 点开高级选项,取消启用数据库 SSL 服务端证书验证
  • 创建管理员账号和密码

PixPin_2025-02-26_23-44-30

10.注意事项:

  1. 确保防火墙允许 80 端口访问:
1
2
3
4
apt install ufw
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
  1. 如果遇到权限问题,可以检查:
1
2
chmod -R 755 /var/www/html/typecho
chown -R www-data:www-data /var/www/html/typecho
  1. 如果网站打不开,可以查看日志:
1
tail -f /var/log/nginx/error.log
  1. 记得定期备份数据:
1
mysqldump -u root -p typecho > typecho_backup.sql

11.常用指令

首先得进入 mysqlmysql -u root -p

  1. 更改 mysql root 密码:
1
ALTER USER 'root'@'localhost' IDENTIFIED BY 'F)!1x5n1>4>ipf,rUXrQaA1D0d';
  1. 查看数据库中的用户表:
1
SELECT * FROM typecho_users;
  1. 重置管理员 url:
1
UPDATE typecho_users SET url = 'https://新域名' WHERE uid = 1;
  1. 修改管理员用户名:
1
UPDATE typecho_users SET name = '新管理员用户名' WHERE name = 'admin';
  1. 重置管理员密码:
1
UPDATE typecho_users SET password = MD5('新密码') WHERE uid = 1;

参考链接?:https://ecouu.com/archives/41/

windows使用VSCode搭建Spring Boot开发环境

一、相关工具

  1. maven:https://maven.apache.org/download.cgi
  2. JDK:https://www.oracle.com/java/technologies/downloads/?er=221886
  3. vscode:https://code.visualstudio.com/

将maven添加到环境变量中

PixPin_2025-02-26_20-26-02

在环境变量中设置JAVA_HOME,值就是JDK的安装路径

PixPin_2025-02-26_20-24-17

二、vscode插件

  1. Spring Boot Extension Pack
  2. Maven for Java
  3. Java Extension Pack

三、配置maven

打开maven安装目录,进入conf文件目录,例如:D:\apache-maven-3.9.9\conf,修改settings.xml文件,配置镜像源依赖安装目录

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
<?xml version="1.0" encoding="UTF-8"?>

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<!--
| This is the configuration file for Maven. It can be specified at two levels:
|
| 1. User Level. This settings.xml file provides configuration for a single user,
| and is normally provided in ${user.home}/.m2/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -s /path/to/user/settings.xml
|
| 2. Global Level. This settings.xml file provides configuration for all Maven
| users on a machine (assuming they're all using the same Maven
| installation). It's normally provided in
| ${maven.conf}/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -gs /path/to/global/settings.xml
|
| The sections in this sample file are intended to give you a running start at
| getting the most out of your Maven installation. Where appropriate, the default
| values (values used when the setting is not specified) are provided.
|
|-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->

<!-- 指定依赖安装目录 -->
<localRepository>D:/apache-maven-3.9.9/repository</localRepository>


<!-- interactiveMode
| This will determine whether maven prompts you when it needs input. If set to false,
| maven will use a sensible default value, perhaps based on some other setting, for
| the parameter in question.
|
| Default: true
<interactiveMode>true</interactiveMode>
-->

<!-- offline
| Determines whether maven should attempt to connect to the network when executing a build.
| This will have an effect on artifact downloads, artifact deployment, and others.
|
| Default: false
<offline>false</offline>
-->

<!-- pluginGroups
| This is a list of additional group identifiers that will be searched when resolving plugins by
their prefix, i.e.
| when invoking a command line like "mvn prefix:goal". Maven will automatically add the group
identifiers
| "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the
list.
|-->
<pluginGroups>
<!-- pluginGroup
| Specifies a further group identifier to use for plugin lookup.
<pluginGroup>com.your.plugins</pluginGroup>
-->
</pluginGroups>

<!-- TODO Since when can proxies be selected as depicted? -->
<!-- proxies
| This is a list of proxies which can be used on this machine to connect to the network.
| Unless otherwise specified (by system property or command-line switch), the first proxy
| specification in this list marked as active will be used.
|-->
<proxies>
<!-- proxy
| Specification for one proxy, to be used in connecting to the network.
|
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
-->
</proxies>

<!-- servers
| This is a list of authentication profiles, keyed by the server-id used within the system.
| Authentication profiles can be used whenever maven must make a connection to a remote server.
|-->
<servers>
<!-- server
| Specifies the authentication information to use when connecting to a particular server,
identified by
| a unique name within the system (referred to by the 'id' attribute below).
|
| NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings
are
| used together.
|
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>
-->

<!-- Another sample, using keys to authenticate.
<server>
<id>siteServer</id>
<privateKey>/path/to/private/key</privateKey>
<passphrase>optional; leave empty if not used.</passphrase>
</server>
-->
</servers>

<!-- mirrors
| This is a list of mirrors to be used in downloading artifacts from remote repositories.
|
| It works like this: a POM may declare a repository to use in resolving certain artifacts.
| However, this repository may have problems with heavy traffic at times, so people have mirrored
| it to several places.
|
| That repository definition will have a unique id, so we can create a mirror reference for that
| repository, to be used as an alternate download site. The mirror site will be the preferred
| server for that repository.
|-->
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->

<!-- 配置镜像源 -->
<mirror>
<id>huawei</id>
<mirrorOf>*</mirrorOf>
<name>华为开源镜像站</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
</mirror>
<mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror>
</mirrors>

<!-- profiles
| This is a list of profiles which can be activated in a variety of ways, and which can modify
| the build process. Profiles provided in the settings.xml are intended to provide local machine-
| specific paths and repository locations which allow the build to work in the local environment.
|
| For example, if you have an integration testing plugin - like cactus - that needs to know where
| your Tomcat instance is installed, you can provide a variable here such that the variable is
| dereferenced during the build process to configure the cactus plugin.
|
| As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
| section of this document (settings.xml) - will be discussed later. Another way essentially
| relies on the detection of a property, either matching a particular value for the property,
| or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
| value of '1.4' might activate a profile when the build is executed on a JDK version of
'1.4.2_07'.
| Finally, the list of active profiles can be specified directly from the command line.
|
| NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
| repositories, plugin repositories, and free-form properties to be used as configuration
| variables for plugins in the POM.
|
|-->
<profiles>
<!-- profile
| Specifies a set of introductions to the build process, to be activated using one or more of the
| mechanisms described above. For inheritance purposes, and to activate profiles via
<activatedProfiles/>
| or the command line, profiles have to have an ID that is unique.
|
| An encouraged best practice for profile identification is to use a consistent naming convention
| for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
| This will make it more intuitive to understand what the set of introduced profiles is attempting
| to accomplish, particularly when you only have a list of profile id's for debug.
|
| This profile example uses the JDK version to trigger activation, and provides a JDK-specific
repo.
<profile>
<id>jdk-1.4</id>

<activation>
<jdk>1.4</jdk>
</activation>

<repositories>
<repository>
<id>jdk14</id>
<name>Repository for JDK 1.4 builds</name>
<url>http://www.myhost.com/maven/jdk14</url>
<layout>default</layout>
<snapshotPolicy>always</snapshotPolicy>
</repository>
</repositories>
</profile>
-->

<!--
| Here is another profile, activated by the property 'target-env' with a value of 'dev', which
| provides a specific path to the Tomcat instance. To use this, your plugin configuration might
| hypothetically look like:
|
| ...
| <plugin>
| <groupId>org.myco.myplugins</groupId>
| <artifactId>myplugin</artifactId>
|
| <configuration>
| <tomcatLocation>${tomcatPath}</tomcatLocation>
| </configuration>
| </plugin>
| ...
|
| NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
| anything, you could just leave off the <value/> inside the activation-property.
|
<profile>
<id>env-dev</id>

<activation>
<property>
<name>target-env</name>
<value>dev</value>
</property>
</activation>

<properties>
<tomcatPath>/path/to/tomcat/instance</tomcatPath>
</properties>
</profile>
-->
</profiles>

<!-- activeProfiles
| List of profiles that are active for all builds.
|
<activeProfiles>
<activeProfile>alwaysActiveProfile</activeProfile>
<activeProfile>anotherAlwaysActiveProfile</activeProfile>
</activeProfiles>
-->
</settings>

四、setting.json配置

1
2
3
4
5
"java.configuration.maven.userSettings": "D:/apache-maven-3.9.9/conf/settings.xml", 
"maven.executable.path": "D:/apache-maven-3.9.9/bin/mvn",// Maven可执行文件的完整路径
"maven.terminal.useJavaHome": true, // 使用JAVA_HOME环境变量中的JDK
"maven.view": "flat", // 可选,设置Maven依赖视图样式
"java.maven.downloadSources": true,// 自动下载源代码

五、创建Spring Boot项目

快捷键打开命令窗口输入:Spring Initializr: Create a Maven Project,根据提示配置项目

PixPin_2025-02-26_20-09-19

六、启动项目

进入spring boot dashboard

PixPin_2025-02-26_20-11-43

点击启动按钮

PixPin_2025-02-26_20-12-49

使用 Cloudflare 为网站或应用套上 CDN 的完整指南

一、准备工作

  1. 拥有域名
    确保你拥有一个已注册的域名(例如 example.com)。
  2. 网站/服务器在线
    源站(你的服务器)需能通过公网 IP 或域名访问,且运行正常。

二、配置 Cloudflare CDN 步骤

1. 注册 Cloudflare 账号

2. 添加网站到 Cloudflare

  • 在控制台点击 “Add a Site”,输入你的域名(如 example.com),选择免费计划(Free Plan)。

3. 修改域名 DNS 服务器

  • Cloudflare 会要求你将域名的 DNS 服务器(Name Server) 替换为 Cloudflare 提供的地址(例如 alice.ns.cloudflare.combob.ns.cloudflare.com)。
    • 操作位置:在你的域名注册商(如 GoDaddy、阿里云)后台,找到域名管理 → 修改 DNS 服务器。

4. 配置 DNS 解析记录

5. 设置源站信息

  • Cloudflare → SSL/TLS → Overview 中,点击配置

    • 选择 Full (strict) 模式(需源站配置有效 SSL 证书)。

      如果选择了 Full (strict)模式,可以在Origin Server中生成证书,点击Create Certificate,选择RSA(2048),选择有效期,点击生成即可

    • 或选择 Flexible 模式(源站无需 SSL 证书,由 Cloudflare 提供加密)。

6. 启用安全防护

  • Security → Settings 中:
    • 开启 DDoS 防护Web Application Firewall (WAF)
    • 调整安全级别(如“Medium”或“High”)。

三、验证 CDN 是否生效

  1. 检查 DNS 解析
    使用 ping example.com,若返回的 IP 是 Cloudflare 的 IP(如 104.21.xx.xx),说明 DNS 生效。Cloudflare IP 列表
  2. 访问网站
    通过浏览器访问网站,检查加载速度是否提升。

四、注意事项

  1. 如果直接使用IP访问服务器,是没有CDN加速效果的
  2. cloudflare家的优选IP或域名都是使用80或443端口的
  3. 如果ssl开启的是完全严格模式,记得在源服务器上面配置证书,否则无法访问

五、总结

步骤 关键点
1. 修改 DNS 服务器 将域名的 Name Server 改为 Cloudflare 提供的地址
2. 添加 DNS 记录 开启代理(橙色云图标),指向源站 IP 或域名
3. 配置 SSL/TLS 根据源站证书情况选择加密模式
4. 启用安全防护 开启 WAF、DDoS 防护,限制恶意流量