Appearance
curl
例子
bash
## POST
curl -X POST https://httpbin.org/post \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{"name": "Taro Yamada", "job": "Developer", "city": "Tokyo"}' \
## GET
curl https://www.example.comtmux
bash
## 启动一个并给它命名(强烈推荐!)
tmux new -s <会话名>
tmux ls ## 查看所有会话
## 连接到指定名称的会话
tmux attach -t <会话名>
## 彻底关闭一个指定的会话
tmux kill-session -t <会话名>窗口管理:
(以下操作均在tmux会话内进行)
创建新窗口:Ctrl+b c (create)
切换到下一个窗口:Ctrl+b n (next)
切换到上一个窗口:Ctrl+b p (previous)
通过编号切换窗口:Ctrl+b [0-9] (直接按数字)
重命名当前窗口:Ctrl+b , (逗号)
列出所有窗口进行选择:Ctrl+b w (windows)
退出不关闭窗口 Ctrl+b d kill当前窗口 ctrl+b &
at定时任务
bash
## 创建定时任务
echo '命令' | at 时间点
## 例子
echo '/home/user01/ywj/code/new_en_start.sh >> /home/user01/ywj/code/clock_.log 2>&1' | at 03:00
echo '/data0/ywj/code/start_en_cot_fix.sh >> /data0/ywj/code/clock_714.log 2>&1' | at 03:30 AM 2025-07-15
## 获取定时任务队列
atq
## 查看具体任务内容
at -c 18
## 删除定时任务
atrm <任务编号>rsync
rsync (remote sync) 是一个强大的文件同步和备份工具。
- 核心优势: 增量同步。只传输变化的部分,而非整个文件,因此非常快速高效。
- 一句话总结:
cp命令的超级进化版,尤其适合重复性同步任务。
万能组合 rsync -avhP
- -a (archive): 归档模式,最重要、最常用的选项。它保留所有文件属性(权限、时间、所有者等),并递归同步目录。
- -v (verbose): 详细模式,显示同步过程中的文件列表。
- -h (human-readable): 人性化显示,以 K, M, G 为单位显示文件大小。
- -P: 显示进度条 (--progress) 并支持断点续传 (--partial)。
安全与特殊选项:
-n(dry-run): 演习模式。模拟运行,只列出将要执行的操作,但不做任何实际改动。强烈建议在执行重要操作前使用。--delete: 删除模式。让目标目录与源目录完全一致,会删除目标目录中多余的文件。请务必谨慎使用。--exclude: 排除。忽略指定的文件或目录,例如--exclude="*.log"。
基本格式: rsync [选项] <源路径> <目标路径>关键点:源路径后的斜杠 /
source/:只复制source目录下的内容到目标。source:将source目录本身复制到目标。
bash
## 1 本地备份(最常用)
## 将 source 目录里的所有内容同步到 destination 目录
rsync -avhP /path/to/source/ /path/to/destination/
## 2 本地推送到远程服务器
rsync -avhP local_folder/ user@remote_host:/remote_path/
## 3 从远程服务器拉取到本地
rsync -avhP user@remote_host:/remote_path/ local_folder/
## 4 安全演习(先看再做)
## 预览将要执行的操作,但什么也不做
rsync -avhn --delete source/ destination/压缩解压
1. tar 命令 (打包与压缩)
tar 命令通过组合不同参数来执行任务。
核心参数含义:
-c: create,创建新的压缩包。-x: extract,从压缩包中解压文件。-z: 通过 gzip 格式进行压缩/解压 (文件后缀为.tar.gz)。-j: 通过 j (bzip2) 格式进行压缩/解压 (文件后缀为.tar.bz2,压缩率更高)。-v: verbose,显示详细的执行过程-f: file,指定要操作的文件名 (这个参数必须放在最后)。-C: Change to directory,指定解压到的目标目录 (注意C是大写)。
常用组合:
压缩为 .tar.gz
bash
## 命令: tar -czvf <压缩包名.tar.gz> <文件/目录>
tar -czvf archive.tar.gz my_folder(含义: create, zip, verbose, file)
解压 .tar.gz
Bash
## 命令: tar -xzvf <压缩包名.tar.gz>
tar -xzvf archive.tar.gz(含义: extract, zip, verbose, file)
2. zip / unzip 命令 (兼容 Windows)
zip 和 unzip 的参数相对简单。
核心参数含义:
zip -r: recursive,递归压缩。当压缩一个文件夹时,此参数是必需的。unzip -d: destination directory,指定一个目标目录来存放解压后的文件。
常用组合:
- 压缩为
.zip
Bash
## 压缩文件夹 (必须加 -r)
zip -r archive.zip my_folder- 解压
.zip
Bash
## 解压到指定目录 (使用 -d)
unzip archive.zip -d /path/to/destination3. gzip
针对 .gz 文件
bash
## 压缩
gzip $file_name
## 解压
## k 参数为保留原始.gz文件
gzip -d[k] $file_namegit
bash
## 查看所有分支,包括 远程
git branch -a
## 删除远程分支
git push $remote_repo_name -d $branch_name
## 允许无关历史提交/合并,加上--allow-unrelated-histories 参数
git merge uuy --allow-unrelated-histories模型部署
模型下载
hfd 脚本 网站 HF-Mirror
bash
## 安装 aria2
sudo apt update
sudo apt install aria2
## 修改 镜像地址
$env:HF_ENDPOINT = "https://hf-mirror.com"bash
./hfd.sh $mid --hf_username XXX --hf_token hf_XXXhuggface
模型部署
vllm 对于语言模型
bash
CUDA_VISIBLE_DEVICES=4,5,6,7 python -m vllm.entrypoints.openai.api_server \
--model [填写模型路径] \
--trust-remote-code \
--tensor-parallel-size 4 \
--port 11436 \
--gpu-memory-utilization 0.9 \
--host 0.0.0.0对于非语言模型
template.jinja 具体模型具体获取
bash
CUDA_VISIBLE_DEVICES=4,5,6,7 python -m vllm.entrypoints.openai.api_server \
--model /data0/ywj/vllm/CodeQwen1.5-7B \
--trust-remote-code \
--tensor-parallel-size 4 \
--port 11437 \
--chat-template /data0/ywj/vllm/template.jinja \
--gpu-memory-utilization 0.9 \
--host 0.0.0.0磁盘挂载
bash
## 树状结构清晰地展示所有块设备
lsblk
## 或者 sudo fdisk -l
## 分区 -> 格式化 -> 挂载 -> 设置自动挂载
## 分区 /dev/nvme1n1为硬盘
sudo gdisk /dev/nvme1n1
## 验证分区是否创建成功
lsblk
## 格式化新分区 nvme1n1p1 这个新分区上创建 ext4 文件系统。
sudo mkfs.ext4 /dev/nvme1n1p1
## 创建一个挂-载点目录
sudo mkdir /data1
## 挂载
sudo mount /dev/nvme1n1p1 /data1
## 验证df -h
## 设置开机自动挂载 ## 获取UUID
sudo blkid /dev/nvme1n1p1
## 编辑 /etc/fstab 文件
sudo nano /etc/fstab
## 添加一行
UUID=YOUR_UUID_HERE /data1 ext4 defaults 0 2磁盘状态
bash
## 这是 du 命令的“黄金组合”
du -h --max-depth=1 [具体路径] | sort -hr- -h: --human-readable 作用:将
du命令默认输出的以KB为单位的长串数字,自动转换为对人类更友好的单位,如K(Kilobytes),M(Megabytes),G(Gigabyte) - -r: 排序反转
软硬链接
| 特性 | 硬链接 (Hard Link) | 软链接 (Symbolic Link) |
|---|---|---|
| 本质 | 文件的多个别名 | 文件的快捷方式 |
| Inode | 共享同一个 Inode | 有自己独立的 Inode |
| 删除源文件 | 不影响其他链接,数据依然存在 | 链接失效 |
| 跨分区 | 不可以 | 可以 |
| 链接目录 | 不可以 | 可以 |
软链接 (Symbolic Links)
核心思想: 创建一个指针文件,指向目标路径。可以理解为 Windows 的快捷方式。
- 创建命令 (
ln -s): 基本语法:ln -s [选项] <源路径> <链接路径
ln : link s: soft 软链接
bash
## 1 创建文件的链接,推荐使用绝对路径,因为它最可靠
ln -s /var/log/app.log ~/app_log_shortcut
## 2 创建目录的软链接
ln -s /mnt/data/my_projects ~/projects
## 3 强制覆盖已存在的链接 (`-f`)
ln -sf /etc/app_config/v2 ~/config- 查看与识别命令
bash
## 1
ls -l ## 即可
## 2 readlink: 直接读取软链接指向的路径。
readlink ~/app_log_shortcut ## 输出真实路径
## 递归解析
readlink -f ~/some_nested_link- 删除与维护命令
bash
## rm: 删除软链接本身
rm ~/app_log_shortcut==警告: 绝对不要在链接名后加斜杠 (/),即 rm ~/projects/。这会删除源目录下的所有内容,而不是链接本身!这是一个非常危险的错误。==
bash
## unlink: 专门用于删除链接的命令,比 rm 更安全,因为它只能操作链接。
unlink ~/app_log_shortcut硬链接 (Hard Links)
核心思想: 为同一个文件内容(由同一个 inode 标识)创建多个名字。
- 创建命令 (
ln): 基本语法:ln <源文件> <新链接名>
没有 -s
bash
## 创建硬链接
ln backup.sh backup_alias.sh- 查看与识别命令:
ls -i: 查看文件的 inode 号。硬链接的 inode 号是完全相同的。
bash
ls -i backup.sh backup_alias.sh
## 输出: 524315 backup.sh 524315 backup_alias.sh
## 第一个字段 '524315' 就是 inode 号ls -l: 查看文件的链接数。
bash
ls -l backup.sh
## 输出: -rwxr-xr-x 2 user group 12 Aug 12 10:15 backup.sh
## 第二个字段 '2' 就是链接数,表示有两个文件名指向这个 inodefind -inum: 根据 inode 号查找所有对应的文件名(即所有硬链接)。
bash
## 在当前目录下查找 inode 号为 524315 的所有文件
find . -inum 524315
## 输出:
## ./backup.sh
## ./backup_alias.shstat: 显示文件的完整元数据,包括 inode 和链接数。
bash
stat backup.sh
## 输出中会包含:
## ...
## Inode: 524315 Links: 2
## ...- 删除与维护命令:
bash
## rm: 删除硬链接(即删除一个文件名)
rm backup_alias.sh
## 执行后,再用 ls -l backup.sh 查看,链接数会从 2 减为 1。
## 只有当链接数减为 0 时(即最后一个文件名被删除),文件数据才会被系统回收。端口监听查询
bash
sudo ss -tulnp- -t: 显示 TCP 端口。
- -u: 显示 UDP 端口。
- -l: 只显示正在监听(LISTEN)状态的端口。
- -n: 不解析服务名称,直接显示端口号(例如显示 80 而不是 http),速度更快。
- -p: 显示占用该端口的进程(Process)信息(PID和进程名)。
bash
sudo lsof -i: <端口号>lsof: Linux中一切即文件,lsof 为 list open file的简写
Nmap
Nmap 主要用于:
- 主机发现: 识别网络上存活的主机。
- 端口扫描: 探测目标主机上开放的端口。
- 服务和版本侦测: 确定开放端口上运行的网络服务及其软件版本。
- 操作系统侦测: 识别目标主机的操作系统类型和版本。
- 可交互的脚本引擎(NSE): 通过使用脚本来扩展 Nmap 的功能,实现更高级的检测,如漏洞扫描、高级探测等。
bash
nmap [扫描类型] [选项] {目标}主机发现
命令: -sn (Ping Scan)
- 这是最基础的“探活”命令。它只通过 Ping 来检查主机是否在线,而不进行耗时的端口扫描。非常适合快速摸清一个网段内有多少活跃设备。
- 举例: 扫描
192.168.1.0/24网段内的所有在线主机。
bash
nmap -sn 192.168.1.0/24命令: -Pn (No Ping)
- 当目标主机开启了防火墙、禁止 Ping 时,Nmap 可能会误判其为离线。使用
-Pn可以跳过存活检测,强制对目标进行端口扫描。 - 举例: 即使
scanme.nmap.org禁止 Ping,也对其进行扫描。
bash
nmap -Pn scanme.nmap.orgUbuntu开启root登录
sudo passwd root
修改/etc/pam.d/gdm-password 将 auth required pam_succeed_if.so user != root quiet_success 注释
重启即可
Ubuntu 启用 ssh
安装openssh-server
bash
sudo apt update
sudo apt install openssh-server检查启用状态
bash
sudo systemctl status ssh开放ssh防火墙端口 22
允许 root 登录
修改 /etc/ssh/sshd_config 将 PermitRootLogin prohibit-password 改为 PermitRootLogin yes
重启 ssh
bash
sudo systemctl restart sshcmake
在项目文件/下创建
CMakeLists.txt
cmake
## CMake版本要求。建议使用较新版本以获得更多功能。
cmake_minimum_required(VERSION 3.10)
project(OUTPUT_NAME VERSION 1.0 LANGUAGES CXX)
## 将源文件和头文件分开管理,增强可读性。
## source_files 变量用于存储 .cpp 文件。
set(OUTPUT_NAME HelloWorld)
set(source_files
BookShelf/net_main.cpp
BookShelf/net_module.cpp
## 注意:头文件 (.h) 不需要在这里列出
)
## 定义可执行文件目标。
## 使用 ${source_files} 变量,方便统一管理。
add_executable(${OUTPUT_NAME} ${source_files})
## target_include_directories 应该用于指定头文件路径。
## PRIVATE 关键字表示这个包含目录仅用于本目标(HelloWorld)的编译。
target_include_directories(${OUTPUT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/BookShelf
)
## PUBLIC: 当一个目标(例如一个库)使用 PUBLIC 参数来指定包含目录时,这个包含目录不仅对该目标本身可见,也对所有依赖于该目标的其他目标可见。
## PRIVATE: 这个参数指定包含目录只对该目标本身可见。依赖于它的其他目标将无法访问这些目录。
## INTERFACE: 当一个目标使用 INTERFACE 参数时,这个包含目录只对依赖于它的其他目标可见,而对它自己不可见。这通常用于头文件专用库(header-only libraries)
## 设置C++标准,例如C++17。
## 这样可以确保项目使用一致的语言标准,而不是依赖于编译器默认设置。
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 强制要求项目必须使用 CMAKE_CXX_STANDARD 变量所指定的 C++ 标准。
## 链接库。
## target_link_libraries 用于指定链接的库。
## 比如,如果你的项目需要 math 库,可以这样添加:
## target_link_libraries(HelloWorld PRIVATE m)命令行使用
创建 build 文件夹
bash
mkdir build
cd build在 build/ 下执行
bash
cmake ..再执行 make 即可
bash
makevscode配置debug
下载插件

创建工程目录
bash
.
├── build
├── CMakeLists.txt
└── src
├── main.cpp
├── Rectangle.cpp
└── Rectangle.h
2 directories, 4 files- 配置cmake vscode
Ctrl + Shift + P 选择 Cmake: Configure
点击 Scan for kits 自动寻找编译器,再操作一次选GCC 11*

- debug or build 选择这两个按钮即可调试和运行


clash
clash 工作在网络层
bash
wget https://downloads.clash.wiki/ClashPremium/clash-linux-amd64-2023.08.17.gz
##
gzip -d clash-*
## 移动到 /usr/local/bin/路径下, 将文件改为clash
mv clash-* /usr/local/bin/clash
## 验证安装
clash -h- 配置结点
在机场平台复制链接 url1 借助 Subscription Converter 转化为 config.yaml的下载链接 url2
url3 = https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
bash
##
cd ~/.config/clash
##
wget -O config.yaml "url2"
#
wget url3 ## 不一定需要
## 验证配置文件
clash -t -f ~/.config/clash/config.yaml- 启动 方式1 后台窗口启动
bash
clash -d ~/.config/clash/方式2 配置为systemctl
创建 /etc/systemd/system/clash.service 填写systemctl 配置
toml
[Unit]
Description=Clash
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/clash -d /root/.config/clash/ ## 改为实际的位置
Restart=always
[Install]
WantedBy=multi-user.target加载
bash
sudo systemctl daemon-reloadbash
## 启动
sudo systemctl start clash.service
## 设置为开机自启
sudo systemctl enable clash.service
## 验证
sudo systemctl status clash.service- 配置系统代理 方式1 临时代理
bash
export http_proxy="http://127.0.0.1:7890"
export https_proxy="http://127.0.0.1:7890"
export all_proxy="socks5://127.0.0.1:7891"
## 取消方式
unset http_proxy
unset https_proxy
unset all_proxy方式2 永久设置环境变量 将
bash
export http_proxy="http://127.0.0.1:7890"
export https_proxy="http://127.0.0.1:7890"
export all_proxy="socks5://127.0.0.1:7891"添加到 ~/.bashrc文件末尾 加载生效
bash
source ~/.bashrc- 代理测试
bash
curl www.google.com不能使用 ping
ping命令使用的是 ICMP (Internet Control Message Protocol) 协议,它工作在网络层 clash代理配置的应用层
py导出requirement.txt
bash
pip install pipreqs ## pip-reqs是另外一个包
pipreqs .查看系统资源
bash
## 查看CPU ls+cpu
lscpu
## 系统负载
uptime
## 当前时间,运行时间, 当前用户数目,平均负载(最近1min, 5min, 15min) 一个CPU核心为1
20:08:29 up 198 days, 7:51, 2 users, load average: 0.03, 0.01, 0.00
top
ls -ef
## 查看内存
free -h
## 查看磁盘
df -h外部网络测速
bash
##
sudo apt update
sudo apt install speedtest-cli
##
speedtest-cli监控网络流量
bash
sudo apt install nload
nloadssssminiconda安装
==不要==使用root用户
bash
##
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh
##
~/miniconda/bin/conda init ## 加入 ~/.bashrc中
## 重启终端Python 仅限位置的参数
这是 Python 3.8 引入的一项重要功能,它允许我们定义某些函数参数,使其必须通过位置来传递,而不能通过关键字(参数名=值)的形式传递。
核心语法 斜杠 /
在函数定义的参数列表中,使用一个独立的斜杠 / 作为分界线。所有在 / 左侧的参数都将成为“仅限位置的参数”。
基本示例:
python
## 'name' 是一个仅限位置的参数
def greet(name, /):
print(f"Hello, {name}!")
## 正确调用
greet("Alice")
## 错误调用(会引发 TypeError)
## greet(name="Bob")
## username, password: 仅限位置
## access_level: 普通(两者皆可)
## is_admin: 仅限关键字
def create_user(username, password, /, access_level="guest", *, is_admin=False):
print(f"User: {username}, Level: {access_level}, Admin: {is_admin}")
## 合法调用
create_user("charlie", "a_secret_pw", is_admin=True)
create_user("david", "another_pw", access_level="editor")
## 非法调用
## create_user(username="eva", password="pw") ## 'username' 不能用关键字
## create_user("frank", "franks_pw", True) ## 'is_admin' 不能用位置1. API 的稳定性与灵活性: * 库的维护者可以随时更改仅限位置参数的内部名称(例如,将 name 改为 username),而不会破坏任何用户的代码,因为用户根本无法按名称来调用它
2. 强制代码清晰:
* 对于一些含义非常明显、无需名称解释的参数(如 list.append(object)),强制按位置传递可以使调用代码更简洁。
3. 消除歧义:
* 可以防止参数名与 **kwargs 字典中的键名发生意外冲突。
一个函数签名可以同时包含这三类参数,它们的顺序是固定的:
仅限位置, /, 普通, *, 仅限关键字
实用示例:
python
## username, password: 仅限位置
## access_level: 普通(两者皆可)
## is_admin: 仅限关键字
def create_user(username, password, /, access_level="guest", *, is_admin=False):
print(f"User: {username}, Level: {access_level}, Admin: {is_admin}")
## 合法调用
create_user("charlie", "a_secret_pw", is_admin=True)
create_user("david", "another_pw", access_level="editor")
## 非法调用
## create_user(username="eva", password="pw") ## 'username' 不能用关键字
## create_user("frank", "franks_pw", True) ## 'is_admin' 不能用位置后端提升路线(by 张仁)
推荐学习的软件和知识路线:
一、运维方向
1. linux基础知识(不光是基础命令)
2. nginx:至少学习到掌握配置正向代理、反向代理、负载均衡。至于动静分离,调优等暂时不学也没关系,不过感兴趣也可以学。
3. Docker:这个非常值得你学习,不过不是停留在使用层面,而是深入了解原理。比如你要先去学习什么是容器、容器进行时、镜像等这些概念(注意,这几个概念是容器化时代的概念,可不是只有docker有),再去官网学习Docker的原理(这真的是深入原理)
4. Kubernetes:如果你以后想走软件开发方向,除非是小公司,否则100%要用这个,没有例外。(等你出略学过一遍之后,你就可以自行对比它跟SpringCloud是如何解决分布式所带来的各种问题,即二者解决方案分别是什么)
(这个1~4的路线安排其实也就是小中大公司的部署方式变迁,等你学完你就可以感受到了)
二、理论知识
1. 数据库原理: 首当其冲是数据库原理(虽然你之后也会有课程,但我推荐你提前学),最好以MySQL为例子来联系。
2.软件架构演进:https://icyfenix.cn/architecture/architect-history/
三、其他产品
1. Redis:这是软件开发必定使用的软件,必须学,而且面试几乎不可能缺。(有时间的话不要仅停留在使用层面)
2. MyBatis-Plus:这个就不解释了
3. Apache Shiro:做权限验证的,Spring家族里也有一个Spring Security,这两个占据了java权限验证的大半壁江山,但是我推荐学shiro
4. 回顾Spring + SpringMVC + SpringBoot, 可以看官方文档了。
SpringBoot文件传输
java
@GetMapping("/material/{title}/{dir}/{filename}")
public ResponseEntity<Resource> getFile(@PathVariable String title,@PathVariable String dir,@PathVariable String filename) {
// 文件的存储路径
Path filePath = Paths.get(fileProperties.getDestpath() + File.separator + title + File.separator + dir + File.separator + filename);
try {
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists() || resource.isReadable()) {
String contentType = getContentType(filename);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, contentType)
.body(resource);
} else {
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}java
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
public class FileController {
@GetMapping("/files/{filename:.+}")
public ResponseEntity<Resource> serveFile(@PathVariable String filename) {
try {
Path file = Paths.get("path/to/your/files/" + filename).normalize().toAbsolutePath();
Resource resource = new UrlResource(file.toUri());
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/pdf")) // 根据文件类型设置MIME类型
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
}java
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
public class FileController {
@GetMapping("/download/{filename:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
try {
Path file = Paths.get("path/to/your/files/" + filename).normalize().toAbsolutePath();
Resource resource = new UrlResource(file.toUri());
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream")) // 设置MIME类型为二进制流
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
}java
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
@Controller
public class FileDownloadController {
private static final String FILE_DIRECTORY = "/path/to/upload/directory/";
@GetMapping("/download/{fileName:.+}")
@ResponseBody
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
Path filePath = Paths.get(FILE_DIRECTORY).resolve(fileName).normalize();
try {
Resource resource = new org.springframework.core.io.UrlResource(filePath.toUri());
if (resource.exists() || resource.isReadable()) {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} else {
return ResponseEntity.notFound().build();
}
} catch (IOException ex) {
ex.printStackTrace();
return ResponseEntity.status(500).build();
}
}
}java
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
public class FileUploadController {
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "Please select a file to upload.";
}
try {
byte[] bytes = file.getBytes();
String uploadDir = "/path/to/upload/directory/";
File uploadedFile = new File(uploadDir + file.getOriginalFilename());
file.transferTo(uploadedFile);
return "File uploaded successfully!";
} catch (IOException e) {
e.printStackTrace();
return "File upload failed!";
}
}
}主键返回:一般的get方法无法获得insert的数据的主键值,要加上@Options注解才能返回主 键值:写在@Insert上方
Shiro
工作流程:
请求到达时,CustomFilter 首先检查请求头中是否有 Authorization
如果有,创建 CustomToken 并执行登录
CustomRealm 的 doGetAuthenticationInfo 方法验证 token 是否有效(检查 Redis 中是否存在)
认证成功后,doGetAuthorizationInfo 方法从 Redis 中获取权限信息
根据接口的 @RequiresPermissions 注解验证用户是否有权限访问
如果权限验证失败,GlobalExceptionHandler 返回 403 错误
demo代码
yaml
server:
port: 8080
spring:
application:
name: TopBiz
main:
allow-bean-definition-overriding: true
## datasource:
## url: jdbc:mysql://${zxzy.mysql.host}:${zxzy.mysql.port:3306}/${zxzy.mysql.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
## driver-class-name: com.mysql.cj.jdbc.Driver
## username: ${zxzy.mysql.user}
## password: ${zxzy.mysql.pw}
redis:
host: 113.45.186.208
port: 6379
password: zx_service_homework_l2yz
zxzy:
mysql:
host: 47.108.251.97
port: 3306
user: userof_services
pw: zx_service_homework_l2yz
database: zx_top_service_dbxml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- <version>3.3.10</version>-->
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.homework</groupId>
<artifactId>TopBiz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>TopBiz</name>
<description>TopBiz</description>
<properties>
<java.version>21</java.version>
<shiro.version>1.9.1</shiro.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- Gson for JSON serialization -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>java
package com.homework.topbiz.config;
import com.homework.topbiz.shiro.CustomRealm;
import com.homework.topbiz.shiro.CustomFilter;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
import java.util.Arrays;
@Configuration
public class ShiroConfig {
private final RedisTemplate<String, Object> redisTemplate;
public ShiroConfig(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Bean
public CustomRealm customRealm() {
return new CustomRealm(redisTemplate);
}
@Bean
public Authenticator authenticator(CustomRealm customRealm) {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setRealms(Arrays.asList(customRealm));
return authenticator;
}
@Bean
public Authorizer authorizer(CustomRealm customRealm) {
ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
authorizer.setRealms(Arrays.asList(customRealm));
return authorizer;
}
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(1800000); // 30分钟
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
return sessionManager;
}
@Bean
public SecurityManager securityManager(CustomRealm customRealm, Authenticator authenticator, Authorizer authorizer, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm);
securityManager.setAuthenticator(authenticator);
securityManager.setAuthorizer(authorizer);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 配置过滤器链
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
chainDefinition.addPathDefinition("/api/login", "anon");
chainDefinition.addPathDefinition("/**", "custom");
shiroFilterFactoryBean.setFilterChainDefinitionMap(chainDefinition.getFilterChainMap());
// 配置自定义过滤器
shiroFilterFactoryBean.getFilters().put("custom", new CustomFilter());
return shiroFilterFactoryBean;
}
}java
package com.homework.topbiz.shiro;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader("Authorization");
log.info("检查登录尝试,Authorization header: {}", authorization);
return authorization != null;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader("Authorization");
log.info("执行登录,使用token: {}", authorization);
CustomToken token = new CustomToken(authorization);
try {
getSubject(request, response).login(token);
log.info("登录成功");
return true;
} catch (Exception e) {
log.error("登录失败: {}", e.getMessage());
return false;
}
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
log.info("检查访问权限");
if (isLoginAttempt(request, response)) {
try {
boolean success = executeLogin(request, response);
log.info("访问权限检查结果: {}", success);
return success;
} catch (Exception e) {
log.error("访问权限检查失败: {}", e.getMessage());
return false;
}
}
log.warn("未找到Authorization header");
return false;
}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}java
package com.homework.topbiz.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import java.util.Set;
@Slf4j
@Component
public class CustomRealm extends AuthorizingRealm {
private final RedisTemplate<String, Object> redisTemplate;
public CustomRealm(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public boolean supports(AuthenticationToken token) {
log.info("检查是否支持 token 类型: {}", token.getClass().getName());
return token instanceof CustomToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String token = (String) principals.getPrimaryPrincipal();
log.info("开始授权,token: {}", token);
String redisKey = "permissions:" + token;
log.info("从Redis获取权限,key: {}", redisKey);
Set<String> permissions = (Set<String>) redisTemplate.opsForValue().get(redisKey);
log.info("从Redis获取到的权限: {}", permissions);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
if (permissions != null) {
authorizationInfo.setStringPermissions(permissions);
log.info("设置权限到AuthorizationInfo: {}", permissions);
} else {
log.warn("未找到权限信息");
}
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String tokenStr = (String) token.getPrincipal();
log.info("开始认证,token: {}", tokenStr);
String redisKey = "permissions:" + tokenStr;
log.info("检查Redis中是否存在权限,key: {}", redisKey);
boolean hasKey = redisTemplate.hasKey(redisKey);
log.info("Redis中是否存在该token的权限: {}", hasKey);
if (hasKey) {
log.info("认证成功");
return new SimpleAuthenticationInfo(tokenStr, tokenStr, getName());
}
log.warn("认证失败:未找到token对应的权限信息");
return null;
}
}java
package com.homework.topbiz.shiro;
import org.apache.shiro.authc.AuthenticationToken;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomToken implements AuthenticationToken {
private String token;
public CustomToken(String token) {
this.token = token;
log.info("创建 CustomToken: {}", token);
}
@Override
public Object getPrincipal() {
log.info("获取 Principal: {}", token);
return token;
}
@Override
public Object getCredentials() {
log.info("获取 Credentials: {}", token);
return token;
}
}java
package com.homework.topbiz.util;
import java.util.UUID;
public class TokenUtil {
public static String generateToken() {
return UUID.randomUUID().toString().replace("-", "");
}
}java
package com.homework.topbiz.exception;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<Map<String, Object>> handleUnauthorizedException(UnauthorizedException e) {
log.warn("权限验证失败: {}", e.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("status", HttpStatus.FORBIDDEN.value());
response.put("error", "Forbidden");
response.put("message", "您没有权限访问此资源");
return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleException(Exception e) {
log.error("系统异常: ", e);
Map<String, Object> response = new HashMap<>();
response.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
response.put("error", "Internal Server Error");
response.put("message", e.getMessage());
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}java
package com.homework.topbiz.controller;
import com.homework.topbiz.util.TokenUtil;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.Set;
@Slf4j
@RestController
@RequestMapping("/api")
public class AuthController {
private final RedisTemplate<String, Object> redisTemplate;
public AuthController(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
log.info("用户登录请求: username={}", username);
// 这里应该添加实际的用户验证逻辑
if ("admin".equals(username) && "password".equals(password)) {
String token = TokenUtil.generateToken();
log.info("生成token: {}", token);
// 设置用户权限
Set<String> permissions = new HashSet<>();
permissions.add("user:view");
permissions.add("user:edit");
// 将权限存入 Redis String redisKey = "permissions:" + token;
redisTemplate.opsForValue().set(redisKey, permissions);
log.info("权限已存入Redis, key={}, value={}", redisKey, permissions);
// 验证权限是否成功存入
Set<String> savedPermissions = (Set<String>) redisTemplate.opsForValue().get(redisKey);
log.info("验证Redis中的权限: {}", savedPermissions);
return token;
}
log.warn("登录失败: username={}", username);
return "登录失败";
}
@GetMapping("/test")
@RequiresPermissions("user:view")
public String test() {
log.info("访问测试接口");
return "您有权限访问此接口";
}
@GetMapping("/test_ubauth")
@RequiresPermissions("user:viasdfsad")
public String test_ubauth() {
log.info("访问测试接口");
return "您没有权限访问此接口";
}
}