0%

对于我用过的几个发行版本来说,ArchLinux算是天生对程序员亲和的,主要是有这几个原因,首先是官方源中维护了许多的编程环境的包,尤其是以Python维护的最多。其次是有AUR源,有许多人在共同的维护这个源,可以让开箱即用的包越来越多。最后的原因才是他是一个Linux的发行版本。

安装之前

在配置安装环境之前,我们得先对我们镜像源改造一下,这样才能继续我们之后的工作。我们得启用用multilibarchlinuxcn两个源。

multilib

/etc/pacman.conf中的multilib的注释取消了就行

arclinuxcn源

/etc/pacman.conf加入如下配置:

1
2
3
[archlinuxcn]
SigLevel = Optional TrustedOnly
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch

其它说法:

1
2
3
4
5
6
7
8
在终端输入
pacman-mirrors -c China
即可换成国内镜像
原有的被保存在/var/lib/pacman-mirrors/custom-mirrors.json
现在的被保存在/etc/pacman.d/mirrorlist
另外pacman的下载缓存在/var/cache/pacman中
对于始终无法信任的情况,选择Optional TrustAll 即可
原文:https://blog.csdn.net/izzxacbbt/article/details/80150176

yaourt 或者 yay

Yaourt是archlinux方便使用的关键部件之一,但没有被整合到系统安装中的工具。建议在装完系统重启之后,更新完pacman和基本系统之后,就安装这个工具。
最简单安装Yaourt的方式是arclinuxcn源pacman -Syu yaourt

安装pacman -S archlinuxcn-keyring这个包导入秘钥就行。
对于这其中可能存在的两个问题,也就是由使用硬件时钟造成秘钥导入不成功的问题,可以使用以下方法进行解决:

  • 立即同步时间,不用修改系统的时间设置
  • 删除/etc/pacman.d/gnupg文件夹,然后运行pacman-key --initpacman-key --refresh-keys就可以解决这一个问题
    1
    2
    3
    4
    5
    sudo rm -R /etc/pacman.d/gnupg/
    sudo rm -R /root/.gnupg/
    sudo gpg --refresh-keys
    sudo pacman-key --init &&sudo pacman-key --populate archlinux manjaro
    sudo pacman-key --refresh-keys

TIM QQ

yaourt qq

Python

对于Python编程环境来说,ArchLinux默认的Python环境是最新版本的Python3版本,所以在使用的时候需要注意这个问题,对于常见Python环境的安装方法如下:

  • 安装Python,这里是2.7 sudo pacman -S python2

  • 安装Anaconda sudo pacman -S anaconda

  • 安装pyenv sudo pacman -S pyenv

  • 安装Pytcharm sudo yaourt -S pycharm-professional

Java JDK

在ArchLinux中使用Java,可以选择两种JDK的版本,一种是使用openjdk,另外一个是使用Oracle jdk版本,并且在ArchLinux中,可以使用archlinux-java来切换不同的版本。对于Java环境的一些工具集,可以使用如下的命令进行安装。

  • 安装oracle jdk sudo pacman -S jdk
  • 安装openjdksudo pacman -S jdk9-openjdk

安装谷歌浏览器

1
sudo pacman -S google-chrome   #安装chrome

安装maven sudo pacman -S maven

安装gradle sudo pacman -S gradle

安装eclipse ``

安装Ideasudo pacman -S intellij-idea-ultimate-edition

安装wget sudo pacman -S wget

安装vim sudo pacman -S vim

安装netstat\ifconfig等等net工具 sudo pacman -S net-tools

PostgreSQL

1
2
3
4
5
6
sudo pacman -S postgresql
sudo passwd postgres
sudo echo -e "postgres ALL=(ALL) ALL" >> /etc/sudoers
su postgres
sudo chmod +w /var/lib/
initdb --locale en_US.UTF-8 -D '/var/lib/postgres/data'

Redis

1
2
3
4
5
wget http://download.redis.io/releases/redis-5.0.4.tar.gz
tar -zxvf redis-5.0.4.tar.gz
cd redis-5.0.4
sudo pacman -S gcc make
make && make install

wiki-Redis

redis-desktop-manager

sudo pacman -S redis-desktop-manager

Node环境

和大部分的平台的安装配置一样,就是有可能需要配置以下全局的npm包的安装位置和一些目录,可以使用
sudo pacman -S nodejs npm
可以在~/.npmrc中写入如下配置:

$HOME为你home目录路径的全写
1
2
cache=$HOME/.node_modules
prefix=$HOME/.node_modules

markdown Typora yaourt typora

yaourt foxit

小巧强大的截图工具深度截图:sudo pacman -S deepin-screenshot

zsh

强大的shell命令脚本解释器zsh: https://www.zhyong.cn/posts/edab/#安装zsh

you-get

强大的Web内容(视频,音频,图片)下载工具you-get: sudo pacman -S you-get

brook

轻量级跨平台的go语言代理工具brook:
sudo pacman -S brook

aria2

强大的命令行下载工具aria2: sudo pacman -S aria2

fzf

模糊搜素神奇fzf: sudo pacman -S fzf

具体配置查看文档,我的配置是添加alias:alias fzf=“fzf –height 40%”,可在oh-my-zsh配置文件中添加fzf插件。
个人认为通过vim **再按Tab键更好地利用了fzf命令,类似的还有kill **等。

ag

一个速度极快的类似ack的代码搜索工具ag: sudo pacman -S the_silver_searcher

thefuck

自动纠正上一个控制台命令thefuck: sudo pacman -S thefuck

tmux

强大的终端复用工具tmux: sudo pacman -S tmux

tig

字符模式下交互查看git项目tig: sudo pacman -S tig

安装配置Git

1
sudo pacman -S git 

设置个人github信息:

1
2
3
4
git config --global user.name "github昵称" 
git config --global user.email "注册邮箱"
git config --global user.name "maxzhao"
git config --global user.email "1441439636@qq.com"

集合sudo pacman -S python2 anaconda pyenv pycharm-professional maven gradle intellij-idea-ultimate-edition wget vim nodejs npm git openssh file-roller unrar unzip p7zip

安装网易云音乐

sudo pacman -S netease-cloud-music

即时聊天工具——wechat

Github上electronic-wechat开源项目(据说比腾讯官方开发的要好-)

可以直接使用:sudo pacman -S electronic-wechat

在安装前需要安装node.js不然不能使用其中的命令安装
安装 Node.js 的最佳方式是使用 nvm。

  • cURL:

    1
    curl https://raw.github.com/creationix/nvm/master/install.sh | sh
  • Wget:

    1
    wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh

安装完成后,重启终端并执行下列命令即可安装 Node.js。

npm install stable

切换目录到下载git clone下载
git clone https://github.com/geeeeeeeeek/electronic-wechat.git

切换到wechat目录
cd electronic-wechat

安装运行
npm install && npm start

编译安装在本地(Linux选择linux,这个不用解释吧?)

1
2
3
4
npm run build:osx
npm run build:linux
npm run build:win32
npm run build:win64

国内版火狐浏览器 pacman -S firefox firefox-i18n-zh-cn

压缩解压缩 pacman -S file-roller unrar unzip p7zip

Git ssh pacman -S git openssh

安装wps yaourt -S wps-office

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vim /etc/pacman-mirrors.conf
#文末添加
OnlyCountry = China

#然后执行
pacman-mirrors -g

vim /etc/pacman.conf
#文末添加:
SigLevel = Optional TrustedOnly            
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch 

#然后执行:
pacman -S archlinuxcn-keyring

# 最后安装 wps:
sudo pacman -S wps-office

# wps字体
sudo pacman -S ttf-wps-fonts

如果安装上好搜狗和wps后不能输入中文的解决方法:
vim ~/.xprofile    
#在最后添加以下内容

1
2
3
4
5
6
7
8
export LC_ALL=zh_CN.UTF-8
export XIM=fcitx
export XIM_PROGRAM=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"
eval `dbus-launch --sh-syntax --exit-with-session`
exec fcitx &

如果有重复登录不进界面系统的情况,则改为

1
2
3
4
export XIM_PROGRAM=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"

然后reboot即可


VSCode sudo pacman -S visual-studio-code-bin

安装显卡驱动

我的笔记本是Intel的集成显卡和英伟达的独立显卡,这里有一些相关描述:https://wiki.archlinux.org/index.php/NVIDIA_(简体中文)
sudo pacman -S xf86-video-intel
sudo pacman -S xf86-video-nouveau

安装NVIDIA显卡驱动

Manjaro虽然自带硬件管理,但无法在两个显卡之间自由切换,按照ArchLinux Wiki给出的方法,只有Bumblebee方案成功
sudo pacman -S bumblebee bbswitch


把用户添加到bumblebee组里:
sudo gpasswd -a XXX bumblebee      //XXX是用户名


启动bumblebeed服务:
sudo systemctl enable bumblebeed.service

安装依赖
sudo pacman -S bumblebee nvidia opencl-nvidia lib32-nvidia-utils lib32-opencl-nvidia mesa lib32-mesa-libgl xf86-video-intel

配置bumblebee:
编辑vim /etc/bumblebee/bumblebee.conf,修改以下内容:

1
2
3
Driver=nvidia # 指定nvidia
[driver-nvidia]
PMMethod=bbswitch       # 电源管理指定bbswitch

用vim编辑文件的保存方法:
编辑好以后按esc键退出,然后输入英文的冒号再输入wq,即保存并退出
reboot

开启NVIDIA显卡 sudo tee /proc/acpi/bbswitch <<< ON #关闭则是off

查看显卡状态 nvidia-smi

Steam配置
Manjaro自带Steam,双击打开之后出现一个License界面之后再也没有反应,通过谷歌之后发现是缺少依赖:
sudo pacman -S steam-native-runtime
steam_install_workaround
等待依赖安装完成后就可以发现steam已经正常打开

sublimit官方地址

http://www.sublimetext.com/docs/3/linux_repositories.html#pacman

一些软件安装卸载的操作

  • 安装 pacman -S
  • 删除 pacman -R
  • 移除已安装不需要软件包 pacman -Rs
  • 删除一个包,所有依赖 pacman -Rsc
  • 升级包 pacman -Syu
  • 查询包数据库 pacman -Ss
  • 搜索已安装的包 pacman -Qs
  • 显示包大量信息 pacman -Si
  • 本地安装包 pacman -Qi
  • 清理包缓存 pacman -Sc

添加命令打别名,比如ll

1
2
3
vim ~/.bashrc
alias ll='ls -l'

本文地址:arch-gnome 编程环境和其它环境安装

Linux上IDEA激活
curl 与 wget 的区别
无法挂载参考链接
u盘安装manjaro——抛弃Centos的旅程开始

本文地址: https://github.com/maxzhao-it/blog/post/17841/

目的:

快速启动nacos服务。

本人并没有任何 shell 编程的基础。

第一次尝试

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
option="$1"
echo $1
if ["$option" == "start"]; then
echo "start"
elif ["$option" == "stop"]; then
echo "stop"
else
echo "options start|stop"
fi
# 执行 nacos start

毫无疑问—报错:

1
2
3
4
start
/home/maxzhao/script/nacos:行4: [start:未找到命令
/home/maxzhao/script/nacos:行6: [start:未找到命令
options start|stop

猜测:[ ]左右都需要空格

第二次尝试

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
option="$1"
echo $1
if [ "$option" == "start" ]; then
echo "start"
elif [ "$option" == "stop" ]; then
echo "stop"
else
echo "options start|stop"
fi
# 执行 nacos start

执行 nacos start

正确结果

1
2
start+start
start

执行 nacos

正确结果:

1
options start|stop

加入操作指令

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
option="$1"
echo $1
if [ "$option" == "start" ]; then
pwd
elif [ "$option" == "stop" ]; then
echo "stop"
else
echo "options start|stop"
fi

执行 nacos start

正确结果

1
2
start
/home/maxzhao/script

测试宏定义操作执行

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
option="$1"
test_pwd="pw"
echo $1+"$option"
if [ "$option" == "start" ]; then
"$test_pwd"d
elif [ "$option" == "stop" ]; then
echo "stop"
else
echo "options start|stop"
fi

执行 nacos start

正确结果

1
2
start
/home/maxzhao/script

写入操作脚本测试

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
local_addr="/home/maxzhao/soft/nacos/distribution/target/nacos-server-1.0.0/nacos/bin/"
option="$1"
echo $1+"$option"
if [ "$option" == "start" ]; then
sh "$local_addr"startup.sh -m standalone
elif [ "$option" == "stop" ]; then
echo "stop"
else
echo "options start|stop"
fi

执行 nacos start

正确结果

1
2
start
/home/maxzhao/script

正确执行

最后脚本

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
local_addr="/home/maxzhao/soft/nacos/distribution/target/nacos-server-1.0.0/nacos/bin/"
option="$1"
if [ "$option" == "start" ]; then
nohup sh "$local_addr"startup.sh -m standalone &
elif [ "$option" == "stop" ]; then
sh "$local_addr"shutdown.sh
else
echo "options start|stop"
fi

执行 nacos start

查看结果:ps -ef |grep nacos

查询到nacos进程,说明结果正确。

自此我的第二个脚本就完成了。

为什么有第二个没有写第一个呢?

我的第一个脚本就是

1
2
#!/bin/bash
nohup idea &

本文地址: https://github.com/maxzhao-it/blog/post/1421/

对于我用过的几个发行版本来说,ArchLinux算是天生对程序员亲和的,主要是有这几个原因,首先是官方源中维护了许多的编程环境的包,尤其是以Python维护的最多。其次是有AUR源,有许多人在共同的维护这个源,可以让开箱即用的包越来越多。最后的原因才是他是一个Linux的发行版本。

安装之前

在配置安装环境之前,我们得先对我们镜像源改造一下,这样才能继续我们之后的工作。我们得启用用multilibarchlinuxcn两个源。

multilib

/etc/pacman.conf中的multilib的注释取消了就行

arclinuxcn源

/etc/pacman.conf加入如下配置:

1
2
3
[archlinuxcn]
SigLevel = Optional TrustedOnly
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch
1
2
3
sudo pacman -Syy
sudo pacman -S archlinuxcn-keyring
sudo pacman -S antergos-keyring

其它说法:

1
2
3
4
5
6
7
8
9
在终端输入
sudo pacman-mirrors -c China
sudo pacman-mirrors -i -c China -m rank
即可换成国内镜像
原有的被保存在/var/lib/pacman-mirrors/custom-mirrors.json
现在的被保存在/etc/pacman.d/mirrorlist
另外pacman的下载缓存在/var/cache/pacman中
对于始终无法信任的情况,选择Optional TrustAll 即可
原文:https://blog.csdn.net/izzxacbbt/article/details/80150176

排列源

1
2
sudo pacman-mirrors -g   #排列源

yaourt 或者 yay

Yaourt是archlinux方便使用的关键部件之一,但没有被整合到系统安装中的工具。建议在装完系统重启之后,更新完pacman和基本系统之后,就安装这个工具。
最简单安装Yaourt的方式是arclinuxcn源pacman -Syu yaourt

安装pacman -S archlinuxcn-keyring这个包导入秘钥就行。
对于这其中可能存在的两个问题,也就是由使用硬件时钟造成秘钥导入不成功的问题,可以使用以下方法进行解决:

  • 立即同步时间,不用修改系统的时间设置
  • 删除/etc/pacman.d/gnupg文件夹,然后运行pacman-key --initpacman-key --refresh-keys就可以解决这一个问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    sudo rm -R /etc/pacman.d/gnupg/
    sudo rm -R /root/.gnupg/
    sudo gpg --refresh-keys
    sudo pacman-key --init
    sudo pacman-key --populate archlinuxcn
    sudo pacman-key --populate archlinux manjaro
    sudo pacman-key --refresh-keys

    或者


    sudo pacman -Syu haveged
    sudo systemctl start haveged
    sudo systemctl enable haveged

    sudo rm -fr /etc/pacman.d/gnupg
    sudo pacman-key --init
    sudo pacman-key --populate archlinux
    sudo pacman-key --populate archlinuxcn
    sudo pacman-key --populate manjaro

安装 yay

1
sudo pacman -S yay

配置 yay

1
yay --aururl "https://aur.tuna.tsinghua.edu.cn" --save

修改的配置文件位于 ~/.config/yay/config.json ,还可通过以下命令查看修改过的配置:

1
yay -P -g

yay 的常用命令:

1
2
3
4
5
yay -S package # 从 AUR 安装软件包
yay -Rns package # 删除包
yay -Syu # 升级所有已安装的包
yay -Ps # 打印系统统计信息
yay -Qi package # 检查安装的版本

安装打包工具包

1
pacman -S base-devel 

中文字体

1
sudo pacman -S wqy-microhei ttf-dejavu wqy-zenhei wqy-microhei wqy-bitmapfont adobe-source-han-sans-cn-fonts adobe-source-han-serif-cn-fonts

输入法

安装 sunpinyin

1
sudo pacman -S fcitx-im fcitx-configtool  fcitx-sunpinyin

配置

1
2
3
4
5
vim ~/.xprofile

export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"

软件/删除

1
2
#很好用的软件包管理工具
pamac-daemon

TIM QQ

yaourt qq

Python

对于Python编程环境来说,ArchLinux默认的Python环境是最新版本的Python3版本,所以在使用的时候需要注意这个问题,对于常见Python环境的安装方法如下:

1
2
3
4
5
6
7
8
# 安装Python,这里是2.7  
sudo pacman -S python2
# 安装Anaconda
sudo pacman -S anaconda
# 安装pyenv
sudo pacman -S pyenv
# 安装Pytcharm
sudo yaourt -S pycharm-professional
Java JDK

在ArchLinux中使用Java,可以选择两种JDK的版本,一种是使用openjdk,另外一个是使用Oracle jdk版本,并且在ArchLinux中,可以使用archlinux-java来切换不同的版本。对于Java环境的一些工具集,可以使用如下的命令进行安装。

  • 安装oracle jdk
    1
    sudo pacman -S jdk
  • 安装openjdk
    1
    sudo pacman -S jdk8-openjdk

chrome

1
sudo pacman -S google-chrome   #安装chrome

maven

1
sudo pacman -S maven

gradle

1
sudo pacman -S gradle

eclipse

Idea

1
sudo pacman -S intellij-idea-ultimate-edition

postman

1
sudo pacman -S postman

wget

1
sudo pacman -S wget 

vim

1
sudo pacman -S vim 

安装netstat\ifconfig等等net工具

1
2
3
4
5
6
7
8
9
10
11
sudo pacman -S net-tools 
​```bash

#### PostgreSQL
​```bash
sudo pacman -S postgresql
sudo passwd postgres
sudo echo -e "postgres ALL=(ALL) ALL" >> /etc/sudoers
su postgres
sudo chmod +w /var/lib/
initdb --locale en_US.UTF-8 -D '/var/lib/postgres/data'

Redis

1
2
3
4
5
wget http://download.redis.io/releases/redis-5.0.4.tar.gz
tar -zxvf redis-5.0.4.tar.gz
cd redis-5.0.4
sudo pacman -S gcc make
make && make install

wiki-Redis

redis-desktop-manager

1
sudo pacman -S redis-desktop-manager

Node环境

和大部分的平台的安装配置一样,就是有可能需要配置以下全局的npm包的安装位置和一些目录,可以使用

1
sudo pacman -S nodejs npm

可以在~/.npmrc中写入如下配置:

$HOME为你home目录路径的全写
1
2
cache=$HOME/.node_modules
prefix=$HOME/.node_modules

markdown Typora

1
2
# yaourt typora 现在pacman 也加入了 typora
sudo pacman -S typora

yaourt foxit

小巧强大的截图工具深度截图:

1
sudo pacman -S deepin-screenshot

zsh

强大的shell命令脚本解释器zsh: https://www.zhyong.cn/posts/edab/#安装zsh

you-get

强大的Web内容(视频,音频,图片)下载工具you-get:

1
sudo pacman -S you-get

brook

轻量级跨平台的go语言代理工具brook:

1
sudo pacman -S brook

aria2

强大的命令行下载工具aria2:

1
sudo pacman -S aria2

fzf

模糊搜素神奇fzf:

1
sudo pacman -S fzf

具体配置查看文档,我的配置是添加alias:alias fzf=“fzf –height 40%”,可在oh-my-zsh配置文件中添加fzf插件。
个人认为通过vim **再按Tab键更好地利用了fzf命令,类似的还有kill **等。

ag

一个速度极快的类似ack的代码搜索工具ag:

1
sudo pacman -S the_silver_searcher

thefuck

自动纠正上一个控制台命令thefuck:

1
sudo pacman -S thefuck

tmux

强大的终端复用工具tmux:

1
sudo pacman -S tmux

tig

字符模式下交互查看git项目tig:

1
sudo pacman -S tig

安装配置Git

1
sudo pacman -S git 

设置个人github信息:

1
2
3
4
git config --global user.name "github昵称" 
git config --global user.email "注册邮箱"
git config --global user.name "maxzhao"
git config --global user.email "1441439636@qq.com"

集合

1
sudo pacman -S  python2 anaconda pyenv pycharm-professional maven gradle    intellij-idea-ultimate-edition  wget vim nodejs npm git openssh file-roller unrar unzip p7zip

安装网易云音乐

1
sudo pacman -S netease-cloud-music

即时聊天工具——wechat

Github上electronic-wechat开源项目(据说比腾讯官方开发的要好-)

可以直接使用:

1
sudo pacman -S electronic-wechat 

在安装前需要安装node.js不然不能使用其中的命令安装
安装 Node.js 的最佳方式是使用 nvm。

  • cURL:$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh
  • Wget: $ wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh

安装完成后,重启终端并执行下列命令即可安装 Node.js。

1
npm install stable 

切换目录到下载git clone下载

1
git clone https://github.com/geeeeeeeeek/electronic-wechat.git

切换到wechat目录

1
cd electronic-wechat

安装运行

1
npm install && npm start

编译安装在本地(Linux选择linux,这个不用解释吧?)

1
2
3
4
npm run build:osx
npm run build:linux
npm run build:win32
npm run build:win64

国内版火狐浏览器

1
pacman -S firefox firefox-i18n-zh-cn 

压缩解压缩

1
pacman -S file-roller unrar unzip p7zip 

Git ssh

1
pacman -S git openssh 

Git kraken

1
sudo pacman -S gitkraken

安装wps

之前的安装方式,不在使用

1
2
3
4
5
6
#yaourt -S wps-office 
yay -S wps-office
sudo pacman -S ttf-ms-fonts:
yay -S wps-office-fonts
yay -S wps-office-mime
yay -S wps-office-mui-zh-cn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vim /etc/pacman-mirrors.conf
#文末添加
OnlyCountry = China

#然后执行
pacman-mirrors -g

sudo vim /etc/pacman.conf
#文末添加:
SigLevel = Optional TrustedOnly            
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch 

#然后执行:
sudo pacman -S archlinuxcn-keyring

# 最后安装 wps: 这个有时候会获取不到, sudo pacman -Ss wps-office 查询
sudo pacman -S wps-office

# wps字体
sudo pacman -S ttf-wps-fonts

如果安装上好搜狗和wps后不能输入中文的解决方法:
vim ~/.xprofile    
#在最后添加以下内容

1
2
3
4
5
6
7
8
export LC_ALL=zh_CN.UTF-8
export XIM=fcitx
export XIM_PROGRAM=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"
eval `dbus-launch --sh-syntax --exit-with-session`
exec fcitx &

如果有重复登录不进界面系统的情况,则改为

1
2
3
4
export XIM_PROGRAM=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"

然后reboot即可


VSCode

1
sudo pacman -S visual-studio-code-bin

截图工具

1
sudo pacman -S spectacle

KDE安装NVIDIA驱动

打开设置页面,找到硬件设定,选择 Auto Install Proprietary Driver

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
Starting
> Using config 'video-hybrid-intel-nvidia-prime' for device: 0000:01:00.0 (0300:10de:1c91) Display controller nVidia Corporation GP107M [GeForce GTX 1050 3 GB Max-Q]
> Using config 'video-hybrid-intel-nvidia-prime' for device: 0000:00:02.0 (0300:8086:3e9b) Display controller Intel Corporation UHD Graphics 630 (Mobile)
> Dependencies to install: video-modesetting
> Installing dependency video-modesetting...
Sourcing /etc/mhwd-x86_64.conf
Has lib32 support: true
Sourcing /var/lib/mhwd/db/pci/graphic_drivers/video-modesetting/MHWDCONFIG
Processing classid: 0300
Sourcing /var/lib/mhwd/scripts/include/0300
:: Synchronizing package databases...
core is up to date
extra is up to date
community is up to date
multilib is up to date
archlinuxcn is up to date
> Successfully installed dependency video-modesetting
> Installing video-hybrid-intel-nvidia-prime...
Sourcing /etc/mhwd-x86_64.conf
Has lib32 support: true
Sourcing /var/lib/mhwd/db/pci/graphic_drivers/hybrid-intel-nvidia-prime/MHWDCONFIG
Processing classid: 0300
Sourcing /var/lib/mhwd/scripts/include/0300
resolving dependencies...
looking for conflicting packages...

Packages (4) lib32-nvidia-utils-460.56-1 linux59-nvidia-460.56-1 nvidia-prime-1.0-4 nvidia-utils-460.56-2

Total Download Size: 131.55 MiB
Total Installed Size: 447.25 MiB

:: Proceed with installation? [Y/n]
:: Retrieving packages...
downloading nvidia-utils-460.56-2-x86_64.pkg.tar.zst...
downloading nvidia-prime-1.0-4-any.pkg.tar.zst...
downloading linux59-nvidia-460.56-1-x86_64.pkg.tar.zst...
downloading lib32-nvidia-utils-460.56-1-x86_64.pkg.tar.zst...
checking keyring...
checking package integrity...
loading package files...
checking for file conflicts...
checking available disk space...
:: Processing package changes...
installing nvidia-utils...
==> If you run into trouble with CUDA not being available, run nvidia-modprobe first.
Optional dependencies for nvidia-utils
gtk3: nvidia-settings [installed]
xorg-server-devel: nvidia-xconfig
opencl-nvidia: OpenCL support
installing nvidia-prime...
installing lib32-nvidia-utils...
Optional dependencies for lib32-nvidia-utils
lib32-opencl-nvidia
installing linux59-nvidia...
In order to use nvidia module, reboot the system.
:: Running post-transaction hooks...
(1/6) Creating system user accounts...
Creating group nvidia-persistenced with gid 143.
Creating user nvidia-persistenced (NVIDIA Persistence Daemon) with uid 143 and gid 143.
(2/6) Reloading system manager configuration...
(3/6) Arming ConditionNeedsUpdate...
(4/6) Updating module dependencies...
(5/6) Updating Kernel initcpios for Nvidia-DRM...
==> Building image from preset: /etc/mkinitcpio.d/linux59.preset: 'default'
-> -k /boot/vmlinuz-5.9-x86_64 -c /etc/mkinitcpio.conf -g /boot/initramfs-5.9-x86_64.img
==> Starting build: 5.9.16-1-MANJARO
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [block]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
loadkeys: Unable to open file: cn: No such file or directory
-> Running build hook: [filesystems]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-5.9-x86_64.img
==> Image generation successful
==> Building image from preset: /etc/mkinitcpio.d/linux59.preset: 'fallback'
-> -k /boot/vmlinuz-5.9-x86_64 -c /etc/mkinitcpio.conf -g /boot/initramfs-5.9-x86_64-fallback.img -S autodetect
==> Starting build: 5.9.16-1-MANJARO
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [modconf]
-> Running build hook: [block]
-> Running build hook: [keyboard]
-> Running build hook: [keymap]
loadkeys: Unable to open file: cn: No such file or directory
-> Running build hook: [filesystems]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-5.9-x86_64-fallback.img
==> Image generation successful
(6/6) Updating the desktop file MIME type cache...
nvidia-utils: install reason has been set to 'explicitly installed'
nvidia-prime: install reason has been set to
'explicitly installed'
lib32-nvidia-utils: install reason has been set to 'explicitly installed'
linux59-nvidia: install reason has been set to 'explicitly installed'
xorg configuration file: '/etc/X11/mhwd.d/nvidia.conf'
> Successfully installed video-hybrid-intel-nvidia-prime

GNOME安装显卡驱动

我的笔记本是Intel的集成显卡和英伟达的独立显卡,这里有一些相关描述:https://wiki.archlinux.org/index.php/NVIDIA_(简体中文)
sudo pacman -S xf86-video-intel
sudo pacman -S xf86-video-nouveau

GNOME安装NVIDIA显卡驱动

Manjaro虽然自带硬件管理,但无法在两个显卡之间自由切换,按照ArchLinux Wiki给出的方法,只有Bumblebee方案成功

1
sudo pacman -S bumblebee bbswitch

把用户添加到bumblebee组里:
sudo gpasswd -a XXX bumblebee      //XXX是用户名


启动bumblebeed服务:
sudo systemctl enable bumblebeed.service

安装依赖
sudo pacman -S bumblebee nvidia opencl-nvidia lib32-nvidia-utils lib32-opencl-nvidia mesa lib32-mesa-libgl xf86-video-intel

配置bumblebee:
编辑vim /etc/bumblebee/bumblebee.conf,修改以下内容:

1
2
3
Driver=nvidia # 指定nvidia
[driver-nvidia]
PMMethod=bbswitch       # 电源管理指定bbswitch

用vim编辑文件的保存方法:
编辑好以后按esc键退出,然后输入英文的冒号再输入wq,即保存并退出
reboot

开启NVIDIA显卡 sudo tee /proc/acpi/bbswitch <<< ON #关闭则是off

查看显卡状态 nvidia-smi

Steam配置
Manjaro 自带Steam,双击打开之后出现一个License界面之后再也没有反应,通过谷歌之后发现是缺少依赖:
sudo pacman -S steam-native-runtime
steam_install_workaround
等待依赖安装完成后就可以发现steam已经正常打开

sublimit官方地址

http://www.sublimetext.com/docs/3/linux_repositories.html#pacman

一些软件安装卸载的操作

  • 安装 pacman -S
  • 删除 pacman -R
  • 移除已安装不需要软件包 pacman -Rs
  • 删除一个包,所有依赖 pacman -Rsc
  • 升级包 pacman -Syu
  • 查询包数据库 pacman -Ss
  • 搜索已安装的包 pacman -Qs
  • 显示包大量信息 pacman -Si
  • 本地安装包 pacman -Qi
  • 清理包缓存 pacman -Sc

添加命令打别名,比如ll

1
2
3
vim ~/.bashrc
alias ll='ls -l'

其他安装及汇总

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
# deepin 系的软件
sudo pacman -S deepin-picker # 深度取色器
sudo pacman -S deepin-screen-recorder # 录屏软件,可以录制 Gif 或者 MP4 格式
sudo pacman -S deepin-screenshot # 深度截图
sudo pacman -S deepin-system-monitor # 系统状态监控
yay -s deepin-wine-wechat #微信
yay -S deepin-wine-tim
yay -S deepin-wine-baidupan
yay -S deepin.com.thunderspeed #迅雷

# android投屏\安卓投屏神器
sudo pacman -S scrcpy
#GIF
yay -S peek

# 开发软件
sudo pacman -S jdk8-openjdk
sudo pacman -S make
sudo pacman -S cmake
sudo pacman -S clang
sudo pacman -S nodejs
sudo pacman -S npm
sudo pacman -S goland
sudo pacman -S vim
sudo pacman -S maven
sudo pacman -S pycharm-professional # Python IDE
sudo pacman -S intellij-idea-ultimate-edition # JAVA IDE
sudo pacman -S goland # Go IDE
sudo pacman -S visual-studio-code-bin # vscode
sudo pacman -S qtcreator # 一款QT开发软件
sudo pacman -S postman-bin
sudo pacman -S insomnia # REST模拟工具
sudo pacman -S gitkraken # GIT管理工具
sudo pacman -S wireshark-qt # 抓包
sudo pacman -S zeal
sudo pacman -S gitkraken # Git 管理工具

# 办公软件
sudo pacman -S google-chrome
sudo pacman -S foxitreader # pdf 阅读
sudo pacman -S bookworm # 电子书阅读
sudo pacman -S unrar unzip p7zip
sudo pacman -S goldendict # 翻译、取词
sudo pacman -S wps-office
yay -S typora # markdown 编辑
yay -S electron-ssr # 缺少我需要的加密算法
yay -S xmind #思维导图

# 设计
sudo pacman -S pencil # 免费开源界面原型图绘制工具

# 娱乐软件
sudo pacman -S netease-cloud-music #网易云音乐

# 下载软件
sudo pacman -S aria2 #下载神器,类似的还有axel
sudo pacman -S filezilla # FTP/SFTP

# 图形
sudo pacman -S gimp # 修图

# 系统工具
sudo pacman -S albert #类似Mac Spotlight,另外一款https://cerebroapp.com/
yay -S copyq # 剪贴板工具,类似 Windows 上的 Ditto

# 终端
sudo pacman -S screenfetch # 终端打印出你的系统信息,screenfetch -A 'Arch Linux'
sudo pacman -S htop
sudo pacman -S bat #cat的进阶版
sudo pacman -S yakuake # 堪称 KDE 下的终端神器,KDE 已经自带,F12 可以唤醒
sudo pacman -S net-tools # 这样可以使用 ifconfig 和 netstat
yay -S tldr
yay -S tig # 命令行下的 git 历史查看工具
yay -S tree #以树状图列出目录的内容
yay -S ncdu # 命令行下的磁盘分析器,支持Vim操作
yay -S mosh # 一款速度更快的 ssh 工具,网络不稳定时使用有奇效

本文地址:arch-gnome 编程环境和其它环境安装

Linux上IDEA激活
curl 与 wget 的区别
无法挂载参考链接
u盘安装manjaro——抛弃Centos的旅程开始

本文地址: https://github.com/maxzhao-it/blog/post/25441/

元编程

一个健壮的系统都要对外部提交的数据进行完整性、合法性的校验。

校验是我们程序开发中必不可少的过程。

即使开发一个不面对最终用户的工具包,也需要对传入的数据进行缜密的校验来防止引发底层难以追踪的问题。

后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。

不使用Bean Validation校验数据的代码基本都是靠大量的 if-else.所以我这里学习使用了 注解方式实现数据校验.

使用

SpringBoot 中的 bean validation 是集成了hibernate-validator tomcat-embed-el

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

简单的校验

@Valid:常见用在方法,类中字段上进行校验

@Validated:是spring提供的对@Valid的封装,常见用在方法上进行校验:用在类型、方法和方法参数上。但不能用于成员属性(field)

BindingResult:是验证是否错误

Model中

1
2
@Range(max = 150, min = 1, message = "年龄范围应该在1-150内。")
private Integer age;

Controller中

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

绑定多个校验对象

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result,@RequestBody @Valid AppUser appUser2,BindingResult result2){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

部分标签

Bean Validation 中内置的 constraint

注解 作用
@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

注解 作用
@Email 被注释的元素必须是电子邮箱地址
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=, max=) 被注释的元素必须在合适的范围内
@NotBlank 被注释的字符串的必须非空
@URL(protocol=,host=, port=,regexp=, flags=) 被注释的字符串必须是一个有效的url

官方详细

中文详细

容易记错的

@NotNull 任何对象的value不能为null

@NotEmpty 集合对象的元素不为0,即集合不为空,也可以用于字符串不为null

@NotBlank 只能用于字符串不为null,并且字符串trim()以后length要大于0

resource 下新建错误信息配置文件

在resource 目录下新建提示信息配置文件 ValidationMessages.properties

文件中的格式为message=ASCII.

中文的ascii码

hibernate的校验模式

1、普通模式(默认是这个模式)

  普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

2、快速失败返回模式

  快速失败返回模式(只要有一个验证失败,则返回)

两种验证模式配置方式

参考官方文档

failFast:true 快速失败返回模式 false 普通模式

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation
.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
}
}

和 (hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式)

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}

手动验证Model

1
2
3
4
5
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}

分组校验(区分新增和修改的规则)

简单使用

场景

新增用户信息和修改用户信息所需要验证的字段是不同的.

1
2
3
4
5
public interface AppUserVaildC {
}

public interface AppUserVaildU {
}

Model中

Default 是默认分组.

1
2
3
4
5
6
@Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
/**年龄*/
private Integer age;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {AppUserVaildC.class})
/**性别 0:未知;1:男;2:女*/
private Integer sex;

Controller中使用

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Validated({AppUserVaildC.class, AppUserVaildU.class}) AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

普通使用

1
2
3
4
5
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,AppUserVaildC,AppUserVaildU);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}

组序列

除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证

1
2
3
@GroupSequence({AppUserVaildC.class, AppUserVaildU.class, Default.class})
public interface GroupOrder {
}

Controller中使用

1
2
3
4
5
6
7
8
@PostMapping("save")
public void v1(@RequestBody @Validated({GroupOrder.class}) AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}

普通使用

1
2
3
4
5
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,GroupOrder);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}

自定义验证

下面是一个自定义大小写的验证

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
public enum CaseMode {
UPPER,
LOWER;
}


@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
String message() default "";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

CaseMode value();
}


public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
public void initialize(CheckCase checkCase) {
this.caseMode = checkCase.value();
}

public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (s == null) {
return true;
}

if (caseMode == CaseMode.UPPER) {
return s.equals(s.toUpperCase());
} else {
return s.equals(s.toLowerCase());
}
}
}

Model中

1
2
3
4
5
6
@Range(value = CaseMode.LOWER ,message = "年必须是小写",groups={Default.class})
/**年龄*/
private String loginName;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {AppUserVaildC.class})
/**性别 0:未知;1:男;2:女*/
private Integer sex;

validator 配置

1
2
3
4
5
6
7
8
9
10
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

return validator;
}

本文地址: SpringBoot校验(validation)

本文地址: https://github.com/maxzhao-it/blog/post/33696/

格式化

1
2
3
4
String d="08-12月-17 05.38.07.812000 下午";
Locale locale=Locale.CHINA;
Date dd=new SimpleDateFormat("dd-M月-y hh.mm.ss.S a",locale).parse(d);
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(dd));

下面测试输出类型,以及可输出的类型

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
@Test
public void LocalDateTest(){
// region LocalDate
log.debug("LocalDate => ISO_LOCAL_DATE 结果为 {}",LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
log.debug("LocalDate => ISO_DATE 结果为 {}",LocalDate.now().format(DateTimeFormatter.ISO_DATE));
log.debug("LocalDate => ISO_ORDINAL_DATE 结果为 {}",LocalDate.now().format(DateTimeFormatter.ISO_ORDINAL_DATE));
log.debug("LocalDate => ISO_WEEK_DATE 结果为 {}",LocalDate.now().format(DateTimeFormatter.ISO_WEEK_DATE));
log.debug("LocalDate => BASIC_ISO_DATE 结果为 {}",LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE));
// endregion
}


@Test
public void LocalTimeTest(){
// region LocalTime
log.debug("LocalTime => ISO_LOCAL_TIME 结果为 {}",LocalTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME));
log.debug("LocalTime => ISO_TIME 结果为 {}",LocalTime.now().format(DateTimeFormatter.ISO_TIME));
// endregion
}

@Test
public void LocalDateTimeTest(){
// region LocalDateTime
log.debug("LocalDateTime => ISO_LOCAL_DATE 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
log.debug("LocalDateTime => ISO_DATE 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_DATE));
log.debug("LocalDateTime => ISO_LOCAL_TIME 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME));
log.debug("LocalDateTime => ISO_TIME 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_TIME));
log.debug("LocalDateTime => ISO_LOCAL_DATE_TIME 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
log.debug("LocalDateTime => ISO_DATE_TIME 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
log.debug("LocalDateTime => ISO_ORDINAL_DATE 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_ORDINAL_DATE));
log.debug("LocalDateTime => ISO_WEEK_DATE 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.ISO_WEEK_DATE));
log.debug("LocalDateTime => BASIC_ISO_DATE 结果为 {}",LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE));
log.debug("LocalDateTime.parse(\"2019-01-01\", DateTimeFormatter.ISO_DATE_TIME) {}",LocalDateTime.parse("2019-01-01",DateTimeFormatter.ISO_LOCAL_DATE));
// endregion
}

后端接收

直接上代码,三种序列化方式

第一种方式:反序列化(用于json

1
2
3
4
5
6
7
8
9
@Bean
public JsonSerializer<LocalDateTime> basicJsonSerializer(){
return new JsonSerializer<LocalDateTime>(){
@Override
public void serialize(LocalDateTime value,JsonGenerator gen,SerializerProvider serializers)throws IOException{
gen.writeString(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
};
}

第二种方式:Converter转换(只支持parameter

这个只能用于 URL 参数

1
2
3
4
5
6
7
8
9
10
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter(){
return new Converter<String, LocalDateTime>(){
@Override
public LocalDateTime convert(@NotNull String oldValue){
/*处理String*/
return null;
}
};
}

第三种方式:实现WebMvcConfigureraddFormatters

这个也是和第二种方式一样,只不过这里自定义程度高!可以添加FormattersConverter 以及他们的工厂。

下面是部分代码。

1
2
3
4
5
6
7
8
9

@Component
public class BootWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
/*常用类型转换数组*/
BootConverters.getConverterToRegister().forEach(registry::addConverter);
}
}

后端发出:反序列化

一般单个序列化

1
2
3
4
5
6
7
8
9
10
11
@Bean
public JsonDeserializer<LocalDateTime> basicJsonDeserializer(){
return new JsonDeserializer<LocalDateTime>(){
@Override
public LocalDateTime deserialize(JsonParser p,DeserializationContext ctxt)throws IOException,JsonProcessingException{
String oldValue=p.getText();
/*处理String*/
return null;
}
};
}

下面是引入了JSON包:

1
2
3
4
5

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

代码:

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
/**
* Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json
*/
@Bean
public ObjectMapper objectMapper(){
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

/*LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式
* 在这里修改之后,就不用再去全局序列化了*/
JavaTimeModule javaTimeModule=new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class
,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DateBootUtil.DEFAULT_DATETIME_FORMAT)));
javaTimeModule.addSerializer(LocalDate.class
,new LocalDateSerializer(DateTimeFormatter.ofPattern(DateBootUtil.DEFAULT_DATE_FORMAT)));
javaTimeModule.addSerializer(LocalTime.class
,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DateBootUtil.DEFAULT_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDateTime.class
,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DateBootUtil.DEFAULT_DATETIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDate.class
,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DateBootUtil.DEFAULT_DATE_FORMAT)));
javaTimeModule.addDeserializer(LocalTime.class
,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DateBootUtil.DEFAULT_TIME_FORMAT)));
/*Date序列化和反序列化 jackson-datatype-jsr310 包中已经存在 这里就不用再次写了*/
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}

本文地址: https://github.com/maxzhao-it/blog/post/17748/

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
package java.lang;

public class Object {

/* 一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用。*/
private static native void registerNatives();

/* 对象初始化时自动调用此方法*/
static {
registerNatives();
}

/* 返回此 Object 的运行时类。*/
public final native Class<?> getClass();

/*
hashCode 的常规协定是:
1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
*/

public native int hashCode();



equals

判断两个对象是不是相等。该方法遵循如下性质:

  • 自反性:对于任意非空引用x,则x.equals(x)返回true。
  • 对称性:对于任意非空引用x、y,若x.equals(y)返回true,则y.equals(x)返回true。
  • 传递性:对于任意非空引用x、y、z,若x.equals(y)返回true且y.equals(z)返回true,则x.equals(z)返回true。
  • 对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或者始终返回false,没有提供任何信息进行相等比较的对象被修改。
  • 对于任意非空引用x,则x.equals(null)返回false。

重写equals方法必须重写hashCode方法来保证对任意两个对象equals返回值true时,他们的hashCode返回值必须相等。
请注意源码中的实现是“==”号,必要时请重写该方法!

1
2
3
4
5

public boolean equals(Object obj){
return(this==obj);
}

clone

x.clone() != x 是true
一个对象可以被克隆的前提是该对象代表的类实现了Cloneable接口,否者会抛出一个CloneNotSupportedException异常。

调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

克隆是浅复制。

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
    /*本地CLONE方法,用于对象的复制。*/
protected native Object clone()throws CloneNotSupportedException;

/*返回该对象的字符串表示。非常重要的方法*/
public String toString(){
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}

/*唤醒在此对象监视器上等待的单个线程。*/
public final native void notify();

/*唤醒在此对象监视器上等待的所有线程。*/
public final native void notifyAll();


/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。*/
public final void wait()throws InterruptedException{
wait(0);
}



/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。*/
public final native void wait(long timeout)throws InterruptedException;

/* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。*/
public final void wait(long timeout,int nanos)throws InterruptedException{
if(timeout< 0){
throw new IllegalArgumentException("timeout value is negative");
}

if(nanos< 0||nanos>999999){
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}

if(nanos>=500000||(nanos!=0&&timeout==0)){
timeout++;
}

wait(timeout);
}

/*当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
子类一般会重写该方法做一些系统资源清理工作。一个对象只会被调用一次finalize方法。如果finalize方法抛出异常,这个对象的终结将会停止。
*/
protected void finalize()throws Throwable{}
}

本文地址: https://github.com/maxzhao-it/blog/post/60440/

简述问题

我们在 SpringBoot 中使用 @Cacheable 时,在某个方法调用同类中的另一个方法,则另一个方法的缓存不会生效。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
@Cacheable(value = "allUsersCache", key = "#id")
public String init(String id) {
this.init2("self_key");
return "init_";
}

@Override
@Cacheable(value = "userCache", key = "#id")
public String init2(String id) {
return "init2_";
}

我们如果查看结果只能看到 init参与了缓存,而init2没有参与缓存。

原因

SpringBoot@EnableCaching 默认使用的是 PROXY,也就是Spring AOP,那么在同一个类中,方法间的调用并不会出发proxy

如何解决

1、self 方式解决

这种 self 方式是最直接最简单的,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
@Service
public class CustomCacheService implements ICustomCacheService {
/**
* 1. Self-autowired reference to proxified bean of this class.
*/
@Resource
private ICustomCacheService self;

@Override
@Cacheable(value = "allUsersCache", key = "#id")
public String init(String id) {
// 2. call cached method using self-bean
self.init2("self_key");
return "init_";
}

@Override
@Cacheable(value = "userCache", key = "#id")
public String init2(String id) {
return "init2_";
}
}

下面是 AdviceMode源代码,枚举说明也简单直接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Enumeration used to determine whether JDK proxy-based or
* AspectJ weaving-based advice should be applied.
*
* @author Chris Beams
* @since 3.1
* @see org.springframework.scheduling.annotation.EnableAsync#mode()
* @see org.springframework.scheduling.annotation.AsyncConfigurationSelector#selectImports
* @see org.springframework.transaction.annotation.EnableTransactionManagement#mode()
*/
public enum AdviceMode {

/**
* JDK proxy-based advice.
*/
PROXY,

/**
* AspectJ weaving-based advice.
*/
ASPECTJ
}

2、切换为AspectJ

SpringBoot@EnableCaching 默认使用的是 PROXY,我们可以直接切换为 AspectJ

比如:@EnableCaching(mode = AdviceMode.ASPECTJ)

Spring xml中可以配置

1
<cache:annotation-driven mode="aspectj"/>

推荐

SpringBoot整合Redis与Cache与实现
SpringBoot整合Redis及Redis简介和操作

本文地址: https://github.com/maxzhao-it/blog/post/6375/

前言

对于 SpringBoot 自带的 Spring JPA 方式的分页多条件查询, 目前我只掌握了两种,一种貌似还不支持条件的嵌套,下面就开始说明。

表结构得表现一下吧

SQL太长了,放到最后。

JPA 的 repo 操作类

1
2
3
4
5
6
7
8
9
/**
* JpaSpecificationExecutor 这是为了实现第二种查询方式
* @author maxzhao
* @date 2019-05-06
*/
@Repository(value = "appTableRepository")
public interface AppTableRepository extends JpaRepository<AppTable, String>, JpaSpecificationExecutor<AppTable> {

}

测试代码

备注:把 application.*的文件拷贝到 test目录的 resources下。

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
/**
* 测试
* @author maxzhao
* @date 2019-06-06
*/
@RunWith(SpringRunner.class)
// BasicApplication 是启动类
@SpringBootTest(classes = BasicApplication.class)
public class AppColumnServiceTest {
// 如果没有日志可以删掉
private final static Logger logger = LoggerFactory.getLogger(AppTablesRepositoryTest.class);
@Resource(name = "appTableRepository")
private AppTableRepository appTableRepository;

@Before
public void startInit() {
logger.info("---------------- {} -------------------", "AppColumnServiceTest");
}

@After
public void endDestory() { }

@Test
public void listTableTest() {
//Sort.Direction是个枚举有ASC(升序)和DESC(降序)
Sort.Direction sort = Sort.Direction.ASC;
//PageRequest继承于AbstractPageRequest并且实现了Pageable
//获取PageRequest对象 index:页码 从0开始 size每页容量 sort排序方式 "tab_name"->properties 以谁为准排序
// 这里的tabName是实体的属性,不是数据库字段的名称
Pageable pageable = PageRequest.of(0, 5, sort, "tabName");

//region 查询条件设置方式一:
//要匹配的实例对象
AppTable appTable = new AppTable();
appTable.setTabName("app_");
//定义匹配规则 匹配"vendorId"这个属性 exact 精准匹配
// 这里的tabName是实体的属性,不是数据库字段的名称
ExampleMatcher exampleMatcher = ExampleMatcher
.matching()
.withMatcher("tabName", ExampleMatcher.GenericPropertyMatchers.contains());
Example<AppTable> example = Example.of(appTable, exampleMatcher);
//下面加断点,查询结果集
Page<AppTable> page = appTableRepository.findAll(example, pageable);
//获取总页数
page.getTotalPages();
//获取总元素个数
page.getTotalElements();
//获取该分页的列表
page.getContent();
page.getSize();
//endregion

//查询条件设置方式二:
Specification<AppTable> specification = (Specification<AppTable>) (root, query, criteriaBuilder) -> {
// 这里的tabName是实体的属性,不是数据库字段的名称
Path<String> name = root.get("tabName");
//Path<String> alias = root.get("alias");
//查询条件1
Predicate p1 = criteriaBuilder.like(name, "%" + "app_" + "%");
//查询条件2
//Predicate p2 = criteriaBuilder.lt(alias param.getAge());
//查询条件1和2的关系
//Predicate p = criteriaBuilder.and(p1, p2);
return p1;
};
//下面加断点,查询结果集
Page<AppTable> page2 = appTableRepository.findAll(specification,pageable);
//endregion
}
}

代码中需要加断点的地方数据是一样的,如果不一样,设置了排序规则就没有如果。

上面注释也写的很清楚了,这两种查询方式第一种应该是没有嵌套查询的(我没有翻到源码,网上也搜不到案例),第二种也不麻烦,就多继承一个接口,可以实现嵌套查询。

查询方式二的第二种查询方式(单表多条件)

(网上找的案例,不记得地址了,我也只是 cp了这部分代码,上面的测试一下,这个就懂了 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb
List<Predicate> predicates = new ArrayList<>();
if (user.getUserId() != null && !user.getUserId().equals("")) {
predicates.add(cb.like(root.get("userId").as(String.class), "%" + user.getUserId() + "%"));
}
if (user.getUserName() != null && !user.getUserName().equals("")) {
predicates.add(cb.like(root.get("userName").as(String.class), "%" + user.getUserName() + "%"));
}
if (user.getGender() != null && !user.getGender().equals("")) {
predicates.add(cb.like(root.get("gender").as(String.class), "%" + user.getGender() + "%"));
}
if (user.getAge() != null && !user.getAge().equals("")) {
predicates.add(cb.like(root.get("age").as(String.class), "%" + user.getAge() + "%"));
}
Predicate[] pre = new Predicate[predicates.size()];
criteriaQuery.where(predicates.toArray(pre));
return cb.and(predicates.toArray(pre));

查询方式二的第二种查询方式(多表、多条件)

备注:记得设置 @ManyToOne或者 @OneToMany之类的外键关联哦

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
// (Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb)
List<Predicate> list = new ArrayList<>();
//根据userId 查询user
if (StringUtils.isNotBlank(params.getUserId())) {
list.add(cb.equal(root.get("userId").as(String.class), params.getUserId()));
}
//根据userName 模糊查询user
if (StringUtils.isNotBlank(params.getUserName())) {
list.add(cb.like(root.get("userName").as(String.class), "%" + params.getUserName() + "%"));
}
//根据gender 查询user
if (StringUtils.isNotBlank(params.getGender())) {
list.add(cb.equal(root.get("gender").as(String.class), params.getGender()));
}
//根据age>? 查询user
if (StringUtils.isNotBlank(params.getAge())) {
list.add(cb.gt(root.get("age").as(Integer.class), Integer.valueOf(params.getAge())));
}
//根据gradeName 查询user
if (StringUtils.isNotBlank(params.getGradeName())) {
Join<Grade, User> join = root.join("grade", JoinType.LEFT);
list.add(cb.equal(join.get("gradeName"), params.getGradeName()));
}
//根据schoolName 查询user
if (StringUtils.isNotBlank(params.getSchoolName())) {
Join<School, User> join = root.join("grade", JoinType.LEFT);
list.add(cb.equal(join.get("school").get("schoolName"), params.getSchoolName()));
}
Predicate[] pre = new Predicate[list.size()];
criteriaQuery.where(list.toArray(pre));
return cb.and(list.toArray(pre));

不推荐上面这种用法,这么用让微服务颜面何存?

附录:

数据库SQL:

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
-- MySql
create table app_table
(
tab_name varchar(255) not null comment '表名主键'
primary key,
alias varchar(255) null comment '别名',
can_del_data int null comment '是否可以清除数据0否1是',
id_cols varchar(255) null comment '主键列名',
remark varchar(255) null comment '备注',
tab_type int null comment '物理表类型(app 1应用级;cf_ 2配置级;bn 3 业务级;tmp 4临时;5其它数据表)'
)comment '应用表信息' charset = utf8;
-- 数据
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_area', '地区编码表', 0, 'id', '地区编码表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_column', '应用表字段信息', 0, 'id', '应用表字段信息', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_dept', '部门管理', 0, 'id', '部门管理', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_dict', '应用字典表', 0, 'id', '应用字典表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_dict_value', '应用字典属性表', 0, 'id', '应用字典属性表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_log', '系统日志', 0, 'id', '系统日志', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_menu', '菜单表', 0, 'id', '菜单表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role', '角色表', 0, 'id', '角色表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role_group', '角色组表', 0, 'id', '角色组表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role_group_link', '角色与角色组表', 0, 'id', '角色与角色组表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role_menu', '角色菜单表', 0, 'id', '角色菜单表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_table', '应用表信息', 0, 'tab_name', '应用表信息', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_user', '用户表', 0, 'id', '用户表', 1);

create table app_column
(
id varchar(255) not null comment '主键'
primary key,
alias varchar(255) null comment '别名',
app_dict_id bigint null comment '绑定字典主键',
char_length int null comment '内容长度',
col_default varchar(255) null comment '默认值',
col_name varchar(255) null comment '字段名',
col_type varchar(255) null comment '字段类型(用于与使用控件相关)',
col_type_alias varchar(255) null comment '字段类型描述',
is_null int default 1 null comment '是否允许空(1 是 0 否)',
remark varchar(255) null comment '备注',
scale int null comment '精度',
tab_name varchar(255) null comment '表名',
time_format varchar(255) null comment '时间格式'
)comment '应用表字段信息' charset = utf8;


specificationin语句

1
2
3
4
5
6
Path<String> path = root.get("tabName");
CriteriaBuilder.In<String> in = criteriaBuilder.in(path);
in.value("app_table");
in.value("app_column");
criteriaBuilder.and(in);
return criteriaBuilder.and(p1, in);

其它方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   @PersistenceContext
private EntityManager entityManager;
// 查询
entityManager.find(AppTable.class, id);
// 查询列表
entityManager.createQuery(hql).getResultList();
// 查询列表数量,有参数
entityManager.createQuery(hql)
.setParameter(0, tabName)
.setParameter(1, alias).getResultList().size();
// 更新
AppTable appTable = entityManager.find(AppTable.class, id);
appTable.setTabName("X");
entityManager.flush();
// 删除
entityManager.remove(entityManager.find(AppTable.class, id));

本文地址:Spring Boot Jpa 我已知的两种分页多查询条件的查询方式

本文地址: https://github.com/maxzhao-it/blog/post/17224/

Redis 简介

GitHub 地址:https://github.com/antirez/redis

GitHub 介绍:Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, HyperLogLogs, Bitmaps.

对于缓存

  • 内存的速度远远大于硬盘的速度
  • 缓存主要是在获取资源方便性能优化的关键方面
  • Redis 是缓存数据库
  • 缓存未命中解决与防止缓存击穿

缓存更新策略

  1. Cache aside :

    • 思路:先更新数据库,在更新缓存。

    • 问题:一个读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放到缓存,所以,会造成脏数据。

    • 出现此问题的前提:读缓存时缓存失效,而且并发着有一个写操作。

    • 而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

  2. Read through

  • 思路:在查询操作中更新缓存
  1. Write through
    • 思路:有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)
  2. Write behind caching
    • 思路:只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。
    • 实现有点复杂,具体参考《缓存更新的套路》

Redis 实践(复杂缓存)

配置application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
cache:
type: REDIS
redis:
cache-null-values: false
time-to-live: 600000ms
use-key-prefix: true
#缓存名称列表
cache-names: userCache,allUsersCache
redis:
host: 127.0.0.1
port: 6379
database: 0
# 单通道
lettuce:
shutdown-timeout: 200ms
pool:
max-active: 7
max-idle: 7
min-idle: 2
max-wait: -1ms
timeout: 1000

对应的配置类:org.springframework.boot.autoconfigure.data.redis.RedisProperties

添加配置类

这里自定义RedisTemplate的配置类,主要是想使用Jackson替换默认的序列化机制:

自定义序列化

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
@Configuration
public class RedisConfig {
/**
* redisTemplate 默认使用JDK的序列化机制, 存储二进制字节码, 所以自定义序列化类
* @param redisConnectionFactory redis连接工厂类
* @return RedisTemplate
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);

// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

// 设置value的序列化规则和 key的序列化规则
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

使用Cache aside策略的实例

这里只展示使用服务

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
@Service(value = "appUserService")
public class AppUserServiceImpl implements AppUserService {

@Resource(name = "appUserRepository")
private AppUserRepository appUserRepository;

@Resource
private RedisTemplate<String, User> redisTemplate;

/**
* 不做任何操作
* @param appUser 用户
**/
@Override
public AppUser saveOne(AppUser appUser) {
return appUserRepository.save(appUser);
}
/**
* 获取用户信息
* 如果缓存存在,从缓存中获取城市信息
* 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存
*
* @param loginName 用户登录名
* @return 用户
*/
@Override
public AppUser findByLoginName(String loginName) {
ogger.info("获取用户start...");
// 从缓存中获取用户信息
String key = "AppUser:" + loginName;
ValueOperations<String, User> operations = redisTemplate.opsForValue();

// 缓存存在
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
AppUser user = operations.get(key);
logger.info("从缓存中获取了用户 AppUser = " + loginName);
return user;
}
// 缓存不存在,从 DB 中获取
List<AppUser> appUserList = appUserRepository.findByLoginNameEquals(loginName);
// 插入缓存
if(appUserList.size() > 0){
operations.set(key, appUserList.get(0), 10, TimeUnit.SECONDS);
}
return appUserList.size() > 0 ? appUserList.get(0) : null;
}
/**
* 更新用户
* 如果缓存存在,删除
* 如果缓存不存在,不操作
*
* @param user 用户
*/
public void updateUser(AppUser user) {
logger.info("更新用户start...");
appUserRepository.save(user);
// 缓存存在,删除缓存
String key = "AppUser:" + user.getLoginName();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key);
logger.info("更新用户时候,从缓存中删除用户 >> " + user.getLoginName());
}
}
/**
* 删除用户
* 如果缓存中存在,删除
*/
public void deleteById(Long id) {
logger.info("删除用户start...");
AppUser user = appUserRepository.get(id);
appUserRepository.deleteById(id);

// 缓存存在,删除缓存
String key = "AppUser:" + user.getLoginName();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key);
logger.info("删除用户时候,从缓存中删除用户 >> " + user.getLoginName());
}
}
}

Redis + Cache 实践(简单缓存)

Spring缓存支持

Spring定义了org.springframework.cache.CacheManagerorg.springframework.cache.Cache 接口来统一不同缓存技术。 其中CacheManager是Spring提供的各种缓存技术抽象接口,内部使用Cache接口进行缓存的增删改查操作,我们一般不会直接和Cache打交道。

针对不同的缓存技术,Spring有不同的CacheManager实现类,定义如下表:

CacheManager 描述
SimpleCacheManager 使用简单的Collection存储缓存数据,用来做测试用
ConcurrentMapCacheManager 使用ConcurrentMap存储缓存数据
EhCacheCacheManager 使用EhCache作为缓存技术
GuavaCacheManager 使用Google Guava的GuavaCache作为缓存技术
JCacheCacheManager 使用JCache(JSR-107)标准的实现作为缓存技术,比如Apache Commons JCS
RedisCacheManager 使用Redis作为缓存技术

在我们使用任意一个实现的CacheManager的时候,需要注册实现Bean:

1
2
3
4
5
6
7
/**
* EhCache的配置
*/
@Bean
public EhCacheCacheManager cacheManager(CacheManager cacheManager) {
return new EhCacheCacheManager(cacheManager);
}

声明式缓存注解

Spring提供4个注解来声明缓存规则,如下表所示:

注解 说明
@Cacheable 方法执行前先看缓存中是否有数据,如果有直接返回。如果没有就调用方法,并将方法返回值放入缓存
@CachePut 无论怎样都会执行方法,并将方法返回值放入缓存
@CacheEvict 将数据从缓存中删除
@Caching 可通过此注解组合多个注解策略在一个方法上面

@Cacheable 、@CachePut 、@CacheEvict都有value属性,指定要使用的缓存名称,而key属性指定缓存中存储的键。

@EnableCaching 开启缓存。

@Cacheable

这个注解含义是方法结果会被放入缓存,并且一旦缓存后,下一次调用此方法,会通过key去查找缓存是否存在,如果存在就直接取缓存值,不再执行方法。

这个注解有几个参数值,定义如下

参数 解释
cacheNames 缓存名称
value 缓存名称的别名
condition Spring SpEL 表达式,用来确定是否缓存
key SpEL 表达式,用来动态计算key
keyGenerator Bean 名字,用来自定义key生成算法,跟key不能同时用
unless SpEL 表达式,用来否决缓存,作用跟condition相反
sync 多线程同时访问时候进行同步

在计算key、condition或者unless的值得时候,可以使用到以下的特有的SpEL表达式

表达式 解释
#result 表示方法的返回结果
#root.method 当前方法
#root.target 目标对象
#root.caches 被影响到的缓存列表
#root.methodName 方法名称简称
#root.targetClass 目标类
#root.args[x] 方法的第x个参数

@CachePut

该注解在执行完方法后会触发一次缓存put操作,参数跟@Cacheable一致

@CacheEvict

该注解在执行完方法后会触发一次缓存evict操作,参数除了@Cacheable里的外,还有个特殊的allEntries, 表示将清空缓存中所有的值。

缓存注解使用

在service中定义增删改的几个常见方法,通过注解实现缓存:

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
@Service
@Transactional
public class UserService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private AppuserRepository appuserRepository;

/**
* cacheNames 设置缓存的值
* key:指定缓存的key,这是指参数id值。key可以使用spEl表达式
*
* @param id
* @return
*/
@Cacheable(value = "userCache", key = "#id", unless="#result == null")
public AppUser getById(int id) {
logger.info("获取用户start...");
return appuserRepository.selectById(id);
}

@Cacheable(value = "allUsersCache", unless = "#result.size() == 0")
public List<User> getAllUsers() {
logger.info("获取所有用户列表");
return appuserRepository.findByLoginNameEquals(null);
}

/**
* 创建用户,同时使用新的返回值的替换缓存中的值
* 创建用户后会将allUsersCache缓存全部清空
*/
@Caching(
put = {@CachePut(value = "userCache", key = "#user.id")},
evict = {@CacheEvict(value = "allUsersCache", allEntries = true)}
)
public AppUser createUser(AppUser user) {
logger.info("创建用户start..., user.id=" + user.getId());
appuserRepository.save(user);
return user;
}

/**
* 更新用户,同时使用新的返回值的替换缓存中的值
* 更新用户后会将allUsersCache缓存全部清空
*/
@Caching(
put = {@CachePut(value = "userCache", key = "#user.id")},
evict = {@CacheEvict(value = "allUsersCache", allEntries = true)}
)
public AppUser updateUser(Appuser user) {
logger.info("更新用户start...");
appuserRepository.save(user);
return user;
}

/**
* 对符合key条件的记录从缓存中移除
* 删除用户后会将allUsersCache缓存全部清空
*/
@Caching(
evict = {
@CacheEvict(value = "userCache", key = "#id"),
@CacheEvict(value = "allUsersCache", allEntries = true)
}
)
public void deleteById(int id) {
logger.info("删除用户start...");
appuserRepository.deleteById(id);
}

}

缓存配置类 RedisCacheConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
@EnableCaching
public class RedisCacheConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private Environment env;

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConf = new RedisStandaloneConfiguration();
redisConf.setHostName(env.getProperty("spring.redis.host"));
redisConf.setPort(Integer.parseInt(env.getProperty("spring.redis.port")));
redisConf.setPassword(RedisPassword.of(env.getProperty("spring.redis.password")));
return new LettuceConnectionFactory(redisConf);
}

@Bean
public RedisCacheConfiguration cacheConfiguration() {
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.disableCachingNullValues();
return cacheConfig;
}

@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager rcm = RedisCacheManager.builder(redisConnectionFactory())
.cacheDefaults(cacheConfiguration())
.transactionAware()
.build();
return rcm;
}
}

keyGenerator 自定义key

一般来讲我们使用key属性就可以满足大部分要求,但是如果你还想更好的自定义key,可以实现keyGenerator。

这个属性为定义key生成的类,和key属性不能同时存在。

RedisCacheConfig配置类中添加我自定义的KeyGenerator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 自定义缓存key的生成类实现
*/
@Bean(name = "myKeyGenerator")
public KeyGenerator myKeyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... params) {
logger.info("自定义缓存,使用第一参数作为缓存key,params = " + Arrays.toString(params));
// 仅仅用于测试,实际不可能这么写
return params[0];
}
};
}

切换缓存技术

得益于SpringBoot的自动配置机制,切换缓存技术除了替换相关maven依赖包和配置Bean外,使用方式和实例中一样, 不需要修改业务代码。如果你要切换到其他缓存技术非常简单。

EhCache

当我们需要使用EhCache作为缓存技术的时候,只需要在pom.xml中添加EhCache的依赖:

1
2
3
4
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcahe</artifactId>
</dependency>

EhCache的配置文件ehcache.xml只需要放到类路径下面,SpringBoot会自动扫描,例如:

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
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false" monitoring="autodetect"
dynamicConfig="true">

<diskStore path="java.io.tmpdir/ehcache"/>

<defaultCache
maxElementsInMemory="50000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>

<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>

SpringBoot会为我们自动配置EhCacheCacheManager这个Bean,不过你也可以自己定义。

Guava

当我们需要Guava作为缓存技术的时候,只需要在pom.xml中增加Guava的依赖即可:

1
2
3
4
5
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>

SpringBoot会为我们自动配置GuavaCacheManager这个Bean。

Redis

最后还提一点,本篇采用Redis作为缓存技术,添加了依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

SpringBoot会为我们自动配置RedisCacheManager这个Bean,同时还会配置RedisTemplate这个Bean。 后面这个Bean就是下一篇要讲解的操作Redis数据库用,这个就比单纯注解缓存强大和灵活的多了。

参考文章

Spring Boot Redis Cache

SpringBoot系列 - 缓存

本文地址: SpringBoot整合Redis与Cache与实现

推荐

SpringBoot整合Redis及Redis简介和操作

本文地址: https://github.com/maxzhao-it/blog/post/3728/