Skip to content

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.com

tmux

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)

zipunzip 的参数相对简单。

核心参数含义:

  • 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/destination

3. gzip

针对 .gz 文件

bash
## 压缩
gzip $file_name

## 解压 
## k 参数为保留原始.gz文件
gzip -d[k] $file_name

git

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_XXX

huggface

模型部署

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
删除源文件不影响其他链接,数据依然存在链接失效
跨分区不可以可以
链接目录不可以可以

核心思想: 创建一个指针文件,指向目标路径。可以理解为 Windows 的快捷方式。

  1. 创建命令 (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
  1. 查看与识别命令
bash
## 1 
ls -l ## 即可

## 2 readlink: 直接读取软链接指向的路径。
readlink ~/app_log_shortcut ## 输出真实路径  

## 递归解析
readlink -f ~/some_nested_link
  1. 删除与维护命令
bash
## rm: 删除软链接本身
rm ~/app_log_shortcut

==警告: 绝对不要在链接名后加斜杠 (/),即 rm ~/projects/。这会删除源目录下的所有内容,而不是链接本身!这是一个非常危险的错误。==

bash
## unlink: 专门用于删除链接的命令,比 rm 更安全,因为它只能操作链接。
unlink ~/app_log_shortcut

核心思想: 为同一个文件内容(由同一个 inode 标识)创建多个名字。

  1. 创建命令 (ln): 基本语法: ln <源文件> <新链接名>

没有 -s

bash
## 创建硬链接 
ln backup.sh backup_alias.sh
  1. 查看与识别命令: 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' 就是链接数,表示有两个文件名指向这个 inode

find -inum: 根据 inode 号查找所有对应的文件名(即所有硬链接)。

bash
## 在当前目录下查找 inode 号为 524315 的所有文件
find . -inum 524315
## 输出:
## ./backup.sh
## ./backup_alias.sh

stat: 显示文件的完整元数据,包括 inode 和链接数。

bash
stat backup.sh
## 输出中会包含:
## ...
## Inode: 524315  Links: 2
## ...
  1. 删除与维护命令:
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.org

Ubuntu开启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 ssh

cmake

在项目文件/下创建

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
make

vscode配置debug

  1. 下载插件

  2. 创建工程目录

bash
.
├── build
├── CMakeLists.txt
└── src
    ├── main.cpp
    ├── Rectangle.cpp
    └── Rectangle.h
2 directories, 4 files
  1. 配置cmake vscode

Ctrl + Shift + P 选择 Cmake: Configure

点击 Scan for kits 自动寻找编译器,再操作一次选GCC 11*

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

clash

clash 工作在网络层

  1. 下载 clash https://downloads.clash.wiki/ClashPremium/
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
  1. 配置结点

在机场平台复制链接 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. 启动 方式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-reload
bash
## 启动
sudo systemctl start clash.service
## 设置为开机自启
sudo systemctl enable clash.service
## 验证
sudo systemctl status clash.service
  1. 配置系统代理 方式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
  1. 代理测试
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
nloadssss

miniconda安装

==不要==使用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, 可以看官方文档了。

image-20250123144515991

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

工作流程:

  1. 请求到达时,CustomFilter 首先检查请求头中是否有 Authorization

  2. 如果有,创建 CustomToken 并执行登录

  3. CustomRealm 的 doGetAuthenticationInfo 方法验证 token 是否有效(检查 Redis 中是否存在)

  4. 认证成功后,doGetAuthorizationInfo 方法从 Redis 中获取权限信息

  5. 根据接口的 @RequiresPermissions 注解验证用户是否有权限访问

  6. 如果权限验证失败,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_db
xml
<?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 "您没有权限访问此接口";  
    }  
  
}

END