Ruiyeclub

  • HOME
  • ARCHIVES
  • ABOUT
  • LINKS
🐤SpringBoot学习入门Demo,持续更新中...: https://github.com/ruiyeclub/SpringBoot-Hello

开发必备软件-Typora配合PicGo阿里云图床配置(转)

Posted on 2024-01-04

写博客的时候,刚开始直接在各大平台上直接写,后来还是觉得不太方便,需要在各大平台之间来回切换。于是就改用Typora,但是有个问题就是图片的处理,只能放在本地。想要发布到各大平台,就需要图床。本文结合阿里云OSS、PicGo图床配置、Typora配置,能够做到一次文章编写,各大平台统一输出。

阿里云配置

创建对象存储Bucket

登录阿里云后,选择对象存储

image-20221222125850693

新人开通试用3个月免费

image-20221222130109938

开通后,进入管理控制台

点击Bucket列表,创建Bucket

image-20221222130233633

访问控制开启公共读

image-20221222130527365

访问控制RAM配置

点击右上角AccessKey管理

image-20221222134259058

创建AccessKey,注意这里的AccessKey ID 和 AccessKey Secret保存下来,后面要用到

image-20221222134507363

PicGo下载安装

下载地址

https://picgo.github.io/PicGo-Doc/zh/guide/#%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85

可以选择山东大学的镜像站

https://mirrors.sdu.edu.cn/github-release/Molunerfinn_PicGo

image-20221222133605621

PicGo配置

  • KeyId 、KeySecret 就是上面阿里云的AccessKey ID 和 AccessKey Secret
  • Bucket 就是阿里云的bucket名称

image-20221222134810396

  • 存储区域,对应阿里云Endpoint的前面,我这里是oss-cn-hangzhou
  • 存储路径,自己随便定义一个,比如 img/
  • 自定义域名,对应阿里云 Bucket域名

image-20221222135020880

Typora下载及配置

Typora目前收费,想要免费可使用,可参考笔者的这个下载地址

链接:https://pan.baidu.com/s/1bCxmIPk23R0B9SVCqY37wg?pwd=F93i 提取码:F93i

设置

找到设置,点击图像设置

上传图片的服务,选择PicGo.app

image-20221222135334696

注意:PicGo.app需要填写输入正确的安装路径,填写后可通过旁边的验证功能查看是否填写成功

实际效果

  • 截图软件,粘贴效果,typora直接转换成了阿里云OSS的地址image-20221222140236233
  • 外站粘贴过来的也是同样效果

Node.js模块化选择Commonjs和ES6

Posted on 2023-12-28

Node.js 使用的模块系统是基于 CommonJS 的,但随着时间的推移,ES6 模块也得到了越来越多的支持。以下是关于 CommonJS 和 ES6 模块的简要概述:

1.CommonJS:

  • 最初为 Node.js 设计,所以又称 Node.js 模块系统。
  • 使用 require() 函数来导入模块,使用 module.exports 或 exports 对象来导出模块。

2.ES6 模块:

  • 是 ECMAScript 6 (ES2015) 中引入的新模块系统。
  • 使用 import 语句来导入模块,使用 export 语句来导出模块。
  • 有静态和动态两种导入方式,提供更强的类型检查和代码编译时优化。

请注意,随着Node.js的发展,许多库已经迁移到ES模块,因此确保你使用的库与你当前的Node.js版本兼容是很重要的。

一、搭建ES6模块系统

1.把需要更换的require全部换成import

2.在src下创建一个.babelrc文件

1
2
3
{
"presets":["es2015"]
}

3.在package.json中的一段加上--exec babel-node

1
2
3
4
5
6
7
8
9
10
{
"name": "api",
"version": "1.0.0",
"description": "",
"type": "module", // 添加type类型
"main": "index.js",
"scripts": {
"dev": "nodemon ./src/main.js --exec babel-node",
"test": "echo \"Error: no test specified\" && exit 1 --exec babel-node"
},

4.安装插件 npm install babel-preset-es2015

二、关于两者的引用区别

1.Commonjs:

1
2
3
// moduleA.js  
const message = 'Hello from CommonJS';
module.exports = message;
1
2
3
// main.js  
const moduleA = require('./moduleA');
console.log(moduleA); // 输出 'Hello from CommonJS'

需要注意的是,CommonJS 的 require 函数返回的是一个对象,该对象的属性对应于模块导出的内容。在上面的例子中,moduleA 是一个包含 message 属性的对象。因此,我们可以使用 moduleA.message 来访问导出的内容。

2.ES6:

1
2
// moduleA.js  
export const message = 'Hello from ES6';
1
2
3
// main.js  
import { message } from './moduleA';
console.log(message); // 输出 'Hello from ES6'

需要注意的是,ES6 的 import 语句返回的是导出的内容本身,而不是一个对象。因此,我们可以直接使用导入的内容,而不需要通过属性来访问。


Linux环境下部署node.js项目并使用pm2对项目进行管理

Posted on 2023-12-21

node.js是让JavaScript能运行在服务端的开发平台,pm2是一个Node.js 守护进程管理器,可以用于管理和监控Node.js 应用程序。

一、安装node环境

1.可通过网站下载,进入Node最新版下载 <https://nodejs.org/en/download/current/

  • 通过wget指令下载:wget https://nodejs.org/dist/v13.11.0/node-v13.11.0-linux-x64.tar.xz

  • 解压:tar -xvf node-v13.11.0-linux-x64.tar.xz

  • 测试安装是否成功:cd node-v13.11.0-linux-x64/bin,执行:./node -v

2.添加node和npm软链建立链接(PS:ln命令是一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接):

  • ln -s /www/node-v13.11.0-linux-x64/bin/node /usr/local/bin/node
  • ln -s /www/node-v13.11.0-linux-x64/bin/npm /usr/local/bin/npm

2.1或者是通过改变node包的存放位置进行操作:mv node-v13.11.0-linux-x64 /usr/local/node

再编辑配置文件:

1
2
3
4
5
6
7
vim /etc/profile

按住i键,进入编辑模式

插入:export PATH=$PATH:/usr/local/node/bin

按esc,输入:wq,退出vim编辑器模式

3.使用测试命令:node -v和npm -v

4.加速npm

  • 使用淘宝的cnpm:npm install cnpm -g --registry=https://registry.npm.taobao.org
  • 加cnpm软链:ln -s /www/node-v13.11.0-linux-x64/bin/cnpm /usr/local/bin/cnpm
  • 需要注意,以后使用cnpm去代替npm来执行,比如:cnpm install XXX

二、安装并使用pm2

PM2 是一个流行的进程管理器,是在生产环境中后台运行 nodejs 的首选。它提供了很多的功能和选项,包括进程监控、自动重启、负载平衡等等。使用 PM2 后,我们可以方便地将 nodejs 应用程序后台运行。

1.安装pm2:npm install -g pm2

2.添加pm2软链:ln -s /www/node-v13.11.0-linux-x64/lib/node_modules/pm2/bin/pm2 /usr/local/bin/

3.pm2常用命令:

  • 启动指定应用:pm2 start <script_file|config_file> [options] ,如:pm2 start index.js --name httpServer

  • 停止指定应用:pm2 stop <appName> [options],如:pm2 stop httpServer

  • 查看全部实例:pm2 list ,注意:pm2 stop 某个项目后,该项目还会存在pm2 list 的列表里面, 只是状态是 stop, 要想去掉该项目,用pm2 delete

  • 重启指定应用:pm2 reload|restart <appName> [options],如:pm2 restart httpServer

  • 显示指定应用详情:pm2 show <appName> [options],如:pm2 show httpServer

  • 删除指定应用:pm2 delete <appName> [options],如:pm2 delete httpServer,如果修改应用配置行为,最好先删除应用后,重新启动方才生效,如修改脚本入口文件

  • 杀掉pm2管理的所有进程:pm2 kill

  • 删除pm2日志:pm2 flush

  • 查看指定应用的日志,即标准输出和标准错误pm2 logs <appName>

  • 监控各个应用进程cpu和memory使用情况pm2 monit

  • 如果项目没有启动就执行 start 如果项目正在运行 就执行relaodpm2 startOrReload <appName>

三、踩坑记录

  1. 2024-1-3:试图在服务器上面安装node.js18,查看node版本的时候发生报错:

image-20240103185224803

搜索资料发现是当前系统版本不支持高版本的node.js,查看当前系统版本:

image-20240103185613533

解决办法降级到node.js16…

参考文章:https://juejin.cn/post/7163899309425950751

  1. 2024-10-18:在服务器运行pm2 start index.js命令报错

项目在scripts配置运行环境,无法直接通过运行js文件启动服务。

可通过pm2启动你的npm脚本

1
pm2 start npm --name "你的应用名" -- run start

也可以使用pm2启动python脚本

1
pm2 start python3 --name "你的应用名" -- main.py

这里的–name参数是给你的应用设置一个名字,– run start是告诉pm2运行package.json中定义的start脚本。

如果你的npm脚本是用来启动一个服务器,那么–name参数非常有用,这样你可以随时查看服务状态或者重启服务。


Idea开发必装插件

Posted on 2022-07-27

"工欲善其事必先利其器",分享一下我工作中必备的Idea开发插件

1.Alibaba Java Coding Guidelines

我们很高兴推出阿里巴巴 Java 编码指南,它整合了阿里巴巴集团技术团队多年来的最佳编程实践。大量的 Java 编程团队对跨项目的代码质量提出了苛刻的要求,因为我们鼓励重用和更好地理解彼此的程序。过去我们见过很多编程问题。例如,有缺陷的数据库表结构和索引设计可能会导致软件架构缺陷和性能风险。另一个例子是难以维护的混乱代码结构。此外,未经身份验证的易受攻击的代码很容易受到黑客的攻击。为了解决这些问题,我们为阿里巴巴的 Java 开发人员编写了这份文档。

阿里巴巴代码规范检查插件,有助于我们的代码更加规范。

2.ignore

忽略Git不必要提交的文件

3.Codota Ai

适用于Java和JavaScript的Codota AI自动补全代码插件

Codota基于数百万个开源 Java 程序和您的上下文完成代码行,帮助您更快地编写代码并减少错误。新版 Codota 提供以下功能:

  • 全线AI自动完成
  • 行内和相关的代码示例
  • 基于您自己的编码实践的代码建议

4.Easy Code

EasyCode是基于IntelliJ IDEA Ultimate版开发的一个代码生成插件,主要通过自定义模板(基于velocity)来生成各种你想要的代码。通常用于生成Entity、Dao、Service、Controller。如果你动手能力强还可以用于生成HTML、JS、PHP等代码。理论上来说只要是与数据有关的代码都是可以生成的。

5.JavaDoc

快速生成javadoc文档注释

6.Lombok

主要用途是提供了简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 java 代码,提高编码效率,使代码更简洁。

7.MyBatisPlus

generator code
live template
MybatisPlus plugin from java to xml or from xml to java

8.Nyan Progress Bar

Pretty progress bars with nyan cat for IJ based IDEs.

Nyan Progress Bar

9.One Dark theme

其中One Dark Vivid主题真的很简洁、舒服,看腻了Idea自带的系统,可以考虑换个主题试试。

One Dark Vivid

10.Translation

Translation plugin for IntelliJ based IDEs/Android Studio/HUAWEI DevEco Studio.

translation

11.其他

Python、requirements等开发python的插件,让我可以更方便的在Idea中编写python代码。
smart input pro自动切换输入法
Gsonformat简化JSON和JavaBean之间的转换
Free MyBatis Tool实现mapper间来回跳转


SpringBoot+Vue前后端分离项目部署教程(转)

Posted on 2022-06-01

1.打包后端项目jar包

打开pom.xml文件,修改packaging方式为jar
image.png

点击右侧maven插件 -> package
image.png

打包成功后会在target目录下生成jar包
image.png

2.编写Dockerfile文件

1
2
3
4
5
6
7
8
9
FROM java:8
VOLUME /tmp
ADD blog-springboot-0.0.1.jar blog.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/blog.jar"]
sh
FROM java:8
VOLUME /tmp
ADD blog-springboot-0.0.1.jar blog.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/blog.jar"]

ps:Dockerfile文件不需要后缀,直接为文件格式

3.编写blog-start.sh脚本

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
#源jar路径  
SOURCE_PATH=/usr/local/docker
#docker 镜像/容器名字或者jar名字 这里都命名为这个
SERVER_NAME=blog-springboot-0.0.1.jar
TAG=latest
SERVER_PORT=8080
#容器id
CID=$(docker ps | grep "$SERVER_NAME" | awk '{print $1}')
#镜像id
IID=$(docker images | grep "$SERVER_NAME:$TAG" | awk '{print $3}')
if [ -n "$CID" ]; then
echo "存在容器$SERVER_NAME, CID-$CID"
docker stop $SERVER_NAME
docker rm $SERVER_NAME
fi
# 构建docker镜像
if [ -n "$IID" ]; then
echo "存在$SERVER_NAME:$TAG镜像,IID=$IID"
docker rmi $SERVER_NAME:$TAG
else
echo "不存在$SERVER_NAME:$TAG镜像,开始构建镜像"
cd $SOURCE_PATH
docker build -t $SERVER_NAME:$TAG .
fi
# 运行docker容器
docker run --name $SERVER_NAME -v /usr/local/upload:/usr/local/upload -d -p $SERVER_PORT:$SERVER_PORT $SERVER_NAME:$TAG
echo "$SERVER_NAME容器创建完成"
sh
#源jar路径
SOURCE_PATH=/usr/local/docker
#docker 镜像/容器名字或者jar名字 这里都命名为这个
SERVER_NAME=blog-springboot-0.0.1.jar
TAG=latest
SERVER_PORT=8080
#容器id
CID=$(docker ps | grep "$SERVER_NAME" | awk '{print $1}')
#镜像id
IID=$(docker images | grep "$SERVER_NAME:$TAG" | awk '{print $3}')
if [ -n "$CID" ]; then
echo "存在容器$SERVER_NAME, CID-$CID"
docker stop $SERVER_NAME
docker rm $SERVER_NAME
fi
# 构建docker镜像
if [ -n "$IID" ]; then
echo "存在$SERVER_NAME:$TAG镜像,IID=$IID"
docker rmi $SERVER_NAME:$TAG
else
echo "不存在$SERVER_NAME:$TAG镜像,开始构建镜像"
cd $SOURCE_PATH
docker build -t $SERVER_NAME:$TAG .
fi
# 运行docker容器
docker run --name $SERVER_NAME -v /usr/local/upload:/usr/local/upload -d -p $SERVER_PORT:$SERVER_PORT $SERVER_NAME:$TAG
echo "$SERVER_NAME容器创建完成"

ps:sh文件需要用notepad++转为Unix格式
image.png

4.将文件传输到服务器

image.png

将上述三个文件传输到/usr/local/docker下(手动创建文件夹)
image.png

5.docker运行后端项目

进入服务器/usr/local/docker下,构建后端镜像

1
sh ./blog-start.sh 

image.png
ps:第一次时间可能比较长,耐心等待即可

查看是否构建成功
image.png

可以去测试下接口是否运行成功
image.png
ps:需要重新部署只需重新传jar包,执行sh脚本即可

6.打包前端项目

打开cmd,进入Vue项目路径 -> npm run build
image.png

打包成功后会在目录下生成dist文件
image.png

将Vue打包项目传输到/usr/local/vue下(由于我前台和后台分为两个项目,所以改名dist文件)
image.png

7.nginx配置(有域名选这个)

在/usr/local/nginx下创建nginx.conf文件,格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;

client_max_body_size 50m;
client_body_buffer_size 10m;
client_header_timeout 1m;
client_body_timeout 1m;

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 4;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;

server {
listen 80;
server_name 前台域名;

location / {
root /usr/local/vue/blog;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

location ^~ /api/ {
proxy_pass http://你的ip:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

server {
listen 80;
server_name 后台子域名;

location / {
root /usr/local/vue/admin;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

location ^~ /api/ {
proxy_pass http://你的ip:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

server {
listen 80;
server_name websocket子域名;

location / {
proxy_pass http://你的ip:8080/websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

}

server {
listen 80;
server_name 上传文件子域名;

location / {
alias /usr/local/upload/;
autoindex on;
autoindex_exact_size on;
autoindex_localtime on;
}

}

}

ps:我前台和后台时分为两个域名,所以写了两个server,前端项目路径为之前传输的路径,其他两个为文件上传域名和websocket转发域名。

docker启动nginx服务

1
docker run --name nginx --restart=always -p 80:80 -d -v /usr/local/nginx/nginx.conf:/etc/nginx/nginx.conf -v /usr/local/vue:/usr/local/vue -v /usr/local/upload:/usr/local/upload nginx 

8.nginx配置(无域名选这个)

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
events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;

client_max_body_size 50m;
client_body_buffer_size 10m;
client_header_timeout 1m;
client_body_timeout 1m;

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 4;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;

server {
listen 80;
server_name 你的ip;

location / {
root /usr/local/vue/blog;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

location ^~ /api/ {
proxy_pass http://你的ip:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

server {
listen 81;
server_name 你的ip;

location / {
root /usr/local/vue/admin;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

location ^~ /api/ {
proxy_pass http://你的ip:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

server {
listen 82;
server_name 你的ip;

location / {
proxy_pass http://你的ip:8080/websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

}

server {
listen 83;
server_name 你的ip;

location / {
alias /usr/local/upload/;
autoindex on;
autoindex_exact_size on;
autoindex_localtime on;
}

}

}

docker启动nginx服务

1
docker run --name nginx --restart=always -p 80:80 -p 81:81 -p 82:82 -p 83:83 -d -v /usr/local/nginx/nginx.conf:/etc/nginx/nginx.conf -v /usr/local/vue:/usr/local/vue -v /usr/local/upload:/usr/local/upload nginx 

ps:需要通过ip + 端口号访问项目

9.运行测试

去浏览器测试下是否运行成功
image.png

10.其他设置

进入后台管理 -> 网站管理 -> 其他设置,配置websocket域名
image.png

11.总结

整个前后端分离的部署教程到这里就结束啦,第一次部署可能会比较麻烦,不过后面就轻车熟路了,有问题的可以私聊问我或者在评论区留言。


SpringCloud使用Feign服务通信踩的坑

Posted on 2022-06-01

  fallback熔断器实现了Feign客户端的所有方法,当网络不通或者访问失败时,会自动调用fallback服务降级类中的方法。

启动项目时报错了,具体的报错信息如下:

1
Caused by: java.lang.IllegalStateException: No fallback instance of type class com.xxx.xxx.feign.fallback.RemoteUserFallback found for feign client xxx

报错内容明显是没找到RemoteUserFallBack这个类

1、检查配置文件

1
2
3
feign:
hystrix:
enabled: true # 开启Feign的熔断功能 默认是关闭的

2、启动类上需要@EnableFeignClients注解

1
@EnableFeignClients(basePackages = {"com.xxx.包名"}) //开启Feign并扫描Feign客户端

3、Feign客户端类上使用@FeignClient,通过fallback属性来指明对应熔断器的类名

1
@FeignClient(value = "服务名", fallback = RemoteUserFallback.class,) //声明当前类是一个Feign客户端,并指定请求的服务名

4、fallback熔断器类上需要加注解@Component,确保可以被spring扫描

我报错的原因就是出现在第四步这里,尽管我加了@component注解。SpringBoot在启动的时候 会扫描main类所在包及其子包进行Bean的实例化,但是fallback熔断器类并不在我启动类的子类下面,我这里是通过引入其模块来调用这里面的方法。

所以最后我在启动类上加了@ComponentScan注解:

1
@ComponentScan(basePackages = {"com.xxx"})

OK,成功启动并访问成功。


SpringBoot整合Mail发送邮件&发送模板邮件

Posted on 2022-06-01

  整合mail发送邮件,其实就是通过代码来操作发送邮件的步骤,编辑收件人、邮件内容、邮件附件等等。通过邮件可以拓展出短信验证码、消息通知等业务。

一、pom文件引入依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!--freemarker模板引擎是为了后面发送模板邮件 不需要的可以不引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

二、application.yml文件中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
mail:
host: smtp.qq.xyz #这里换成自己的邮箱类型 例如qq邮箱就写smtp.qq.com
username: xx@qq.com #QQ邮箱
password: xxxxxxxxxxx #邮箱密码或者授权码
protocol: smtp #发送邮件协议
properties.mail.smtp.auth: true
properties.mail.smtp.port: 465 #端口号465或587
properties.mail.smtp.starttls.enable: true
properties.mail.smtp.starttls.required: true
properties.mail.smtp.ssl.enable: true #开启SSL
default-encoding: utf-8
freemarker:
cache: false # 缓存配置 开发阶段应该配置为false 因为经常会改
suffix: .html # 模版后缀名 默认为ftl
charset: UTF-8 # 文件编码
template-loader-path: classpath:/templates/ # 存放模板的文件夹,以resource文件夹为相对路径

邮箱密码暴露在配置文件很不安全,一般都是采取授权码的形式。点开邮箱,然后在账户栏里面点击生成授权码:
image.png

三、编写MailUtils工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
@Component
@Slf4j
public class MailUtils{

/**
* Spring官方提供的集成邮件服务的实现类,目前是Java后端发送邮件和集成邮件服务的主流工具。
*/
@Resource
private JavaMailSender mailSender;

/**
* 从配置文件中注入发件人的姓名
*/
@Value("${spring.mail.username}")
private String fromEmail;

@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;

/**
* 发送文本邮件
* @param to 收件人
* @param subject 标题
* @param content 正文
*/
public void sendSimpleMail(String to, String subject, String content) {
SimpleMailMessage message = new SimpleMailMessage();
//发件人
message.setFrom(fromEmail);
message.setTo(to);
message.setSubject(subject);
message.setText(content);
mailSender.send(message);
}

/**
* 发送html邮件
*/
public void sendHtmlMail(String to, String subject, String content) {
try {
//注意这里使用的是MimeMessage
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(fromEmail);
helper.setTo(to);
helper.setSubject(subject);
//第二个参数:格式是否为html
helper.setText(content, true);
mailSender.send(message);
}catch (MessagingException e){
log.error("发送邮件时发生异常!", e);
}
}

/**
* 发送模板邮件
* @param to
* @param subject
* @param template
*/
public void sendTemplateMail(String to, String subject, String template){
try {
// 获得模板
Template template1 = freeMarkerConfigurer.getConfiguration().getTemplate(template);
// 使用Map作为数据模型,定义属性和值
Map<String,Object> model = new HashMap<>();
model.put("myname","Ray。");
// 传入数据模型到模板,替代模板中的占位符,并将模板转化为html字符串
String templateHtml = FreeMarkerTemplateUtils.processTemplateIntoString(template1,model);
// 该方法本质上还是发送html邮件,调用之前发送html邮件的方法
this.sendHtmlMail(to, subject, templateHtml);
} catch (TemplateException e) {
log.error("发送邮件时发生异常!", e);
} catch (IOException e) {
log.error("发送邮件时发生异常!", e);
}
}

/**
* 发送带附件的邮件
* @param to
* @param subject
* @param content
* @param filePath
*/
public void sendAttachmentsMail(String to, String subject, String content, String filePath) {
try {
MimeMessage message = mailSender.createMimeMessage();
//要带附件第二个参数设为true
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(fromEmail);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);

FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
mailSender.send(message);
}catch (MessagingException e){
log.error("发送邮件时发生异常!", e);
}

}
}

MailUtils其实就是进一步封装Mail提供的JavaMailSender类,根据业务场景可以在工具类里面添加对应的方法,这里提供了发送文本邮件、html邮件、模板邮件、附件邮件的方法。

四、Controller层的实现

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
@Api(tags = "邮件管理")
@RestController
@RequestMapping("/mail")
public class MailController {

@Autowired
private MailUtils mailUtils;

/**
* 发送注册验证码
* @return 验证码
* @throws Exception
*/
@ApiOperation("发送注册验证码")
@GetMapping("/test")
public String send(){
mailUtils.sendSimpleMail("ruiyeclub@foxmail.com","普通文本邮件","普通文本邮件内容");
return "OK";
}

/**
* 发送注册验证码
* @return 验证码
* @throws Exception
*/
@ApiOperation("发送注册验证码")
@PostMapping("/sendHtml")
public String sendTemplateMail(){
mailUtils.sendHtmlMail("ruiyeclub@foxmail.com","一封html测试邮件",
"<div style=\"text-align: center;position: absolute;\" >\n"
+"<h3>\"一封html测试邮件\"</h3>\n"
+ "<div>一封html测试邮件</div>\n"
+ "</div>");
return "OK";
}

@ApiOperation("发送html模板邮件")
@PostMapping("/sendTemplate")
public String sendTemplate(){
mailUtils.sendTemplateMail("ruiyeclub@foxmail.com", "基于模板的html邮件", "hello.html");
return "OK";
}

@ApiOperation("发送带附件的邮件")
@GetMapping("sendAttachmentsMail")
public String sendAttachmentsMail(){
String filePath = "D:\\projects\\springboot\\template.png";
mailUtils.sendAttachmentsMail("xxxx@xx.com", "带附件的邮件", "邮件中有附件", filePath);
return "OK";
}
}

为了方便测试,这里使用了swagger3,详情可以查看SpringBoot整合Swagger3生成接口文档。

发送模板邮件这里,会读取resources下面的templates文件夹,测试中读取的是hello.html,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>freemarker简单示例</title>
</head>
<body>
<h1>Hello Freemarker</h1>
<div>My name is ${myname}</div>
</body>
</html>

五、测试结果

image.png
如果需要达到通过邮件发送验证码的功能,可以使用redis。后台随机生成验证码,然后把用户的主键设为key,验证码的内容设为value,还可以设置个60s过期存储,发送成功后,用户登录通过主键从redis拿到对应的验证码,然后再进行登录验证就好了。

参考链接:[Spring Boot整合邮件配置](Spring Boot整合邮件配置)

GitHub地址:https://github.com/ruiyeclub/SpringBoot-Hello


SpringBoot整合Swagger3生成接口文档

Posted on 2022-06-01

  前后端分离的项目,接口文档的存在十分重要。与手动编写接口文档不同,swagger是一个自动生成接口文档的工具,在需求不断变更的开发环境下,手动编写文档的效率实在太低。与新版的swagger3相比swagger2配置更少,使用更加方便。

一、pom文件中引入Swagger3依赖

1
2
3
4
5
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

二、Application上面加入@EnableOpenApi注解

1
2
3
4
5
6
7
8
@EnableOpenApi
@SpringBootApplication
@MapperScan(basePackages = {"cn.ruiyeclub.dao"})
public class Swagger3Application {
public static void main(String[] args) {
SpringApplication.run(Swagger3Application.class, args);
}
}

三、Swagger3Config的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class Swagger3Config {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger3接口文档")
.description("更多请咨询服务开发者Ray。")
.contact(new Contact("Ray。", "http://www.ruiyeclub.cn", "ruiyeclub@foxmail.com"))
.version("1.0")
.build();
}
}

四、Swagger注解的使用说明

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
@Api:用在请求的类上,表示对类的说明
tags="说明该类的作用,可以在UI界面上看到的注解"
value="该参数没什么意义,在UI界面上也看到,所以不需要配置"

@ApiOperation:用在请求的方法上,说明方法的用途、作用
value="说明方法的用途、作用"
notes="方法的备注说明"

@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值

@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类

@ApiModel:用于响应类上,表示一个返回响应数据的信息
(这种一般用在post创建的时候,使用@RequestBody这样的场景,
请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:用在属性上,描述响应类的属性

Controller层的配置:

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
@Api(tags = "用户信息管理")
@RestController
@RequestMapping("userRecord")
public class UserRecordController extends ApiController {
/**
* 服务对象
*/
@Resource
private UserRecordService userRecordService;

/**
* 分页查询所有数据
* @param page 分页对象
* @param userRecord 查询实体
* @return 所有数据
*/
@ApiOperation("分页查询所有数据")
@GetMapping("page")
public R selectAll(Page<UserRecord> page, UserRecord userRecord) {
return success(this.userRecordService.page(page, new QueryWrapper<>(userRecord)));
}

/**
* 通过主键查询单条数据
* @param id 主键
* @return 单条数据
*/
@ApiOperation("通过主键查询单条数据")
@GetMapping("{id}")
public R selectOne(@PathVariable Serializable id) {
return success(this.userRecordService.getById(id));
}

/**
* 新增数据
* @param userRecord 实体对象
* @return 新增结果
*/
@ApiOperation("新增数据")
@PostMapping("insert")
public R insert(@RequestBody UserRecord userRecord) {
return success(this.userRecordService.save(userRecord));
}

/**
* 修改数据
* @param userRecord 实体对象
* @return 修改结果
*/
@ApiOperation("修改数据")
@PutMapping("update")
public R update(@RequestBody UserRecord userRecord) {
return success(this.userRecordService.updateById(userRecord));
}

/**
* 删除数据
* @param idList 主键结合
* @return 删除结果
*/
@ApiOperation("删除数据")
@DeleteMapping("delete")
public R delete(@RequestParam("idList") List<Long> idList) {
return success(this.userRecordService.removeByIds(idList));
}
}

五、Swagger界面效果

image.png

Swagger的访问路径由port/swagger-ui.html改成了port/swagger-ui/ 或port/swagger-ui/index.html

GitHub地址:https://github.com/ruiyeclub/SpringBoot-Hello


Shiro整合Redis:使用shiro-redis插件踩的坑

Posted on 2022-06-01

  一直想在shiro权限这块加入缓存,使用redis是再合适不过了,恰巧已经有大佬将shiro和redis整合在一起使用了,只需在引入pom文件中引入即可。

1
2
3
4
5
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>

但是是使用的时候,权限配置这块,也就是重写shiro的doGetAuthorizationInfo方法这里,一直进不来,完整的控制台异常信息如下:

1
2
3
org.crazycake.shiro.exception.PrincipalInstanceException: class com.company.project.manage.entity.UserInfo must has getter for field: id
We need a field to identify this Cache Object in Redis. So you need to defined an id field which you can get unique id to identify this principal. For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. For example, getUserId(), getUserName(), getEmail(), etc.
Default value is "id", that means your principal object has a method called "getId()"

大概意思就是UserInfo对象中必须要有id属性,并且要有对应的get方法。在身份验证的时候就已经将userinfo信息传递给了shiro,然后redis做缓存的时候需要key,key的值就与userinfo里面的id值有关。
image.png
点开UserInfo对象一看,尴了个尬,主键的命名使用的是uid
image.png
最后把主键换成id,就运行正常了。

演示项目在我的github上面,shiro-redis插件的整合可以查看:
https://mrbird.cc/Spring-Boot-Shiro%20cache.html


SpringBoot整合Hibernate Validator实现参数验证功能

Posted on 2022-06-01

  在前后端分离的开发模式中,后端对前端传入的参数的校验成了必不可少的一个环节。但是在多参数的情况下,在controller层加上参数验证,会显得特别臃肿,并且会有许多的重复代码。这里可以引用Hibernate Validator来解决这个问题,直接在实体类进行参数校验,验证失败直接返回错误信息给前端,减少controller层的代码量。

一、pom引入Hibernate Validator

1
2
3
4
5
6
<!-- 验证器 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>

二、通过注解在实体类进行参数校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
public class UserModel {

@NotNull(message = "用户名称不能为空!")
private String userName;

@NotNull(message = "age不能为null!")
@Range(min = 1, max = 888, message = "范围为1至888")
private Integer age;

/**
* 日期格式化转换
*/
@NotNull(message = "日期不能为null!")
private Date date;
}

这里用到的参数校验的注解有@NotNull和@Range,message是到时候我们返回给前端的信息,注解的具体意思如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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) 被注释的元素必须符合指定的正则表达式。
@Email 被注释的元素必须是电子邮件地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串必须非空
@Range 被注释的元素必须在合适的范围内

三、controller层的方法加上@Valid注解

1
2
3
4
5
6
7
8
9
@PostMapping("/testPost")
public Object testPost(@RequestBody @Valid UserModel userModel, BindingResult result){
if(result.hasErrors()){
for(ObjectError error:result.getAllErrors()){
return error.getDefaultMessage();
}
}
return userModel;
}

controller层这里只需要在实体类的前面加上@Valid注解,这个注解可以实现数据的验证。这里BindingResult是存储了校验时的错误信息,验证有误时将错误信息返回给前端。这里不使用BindingResult的时候,控制台会报MethodArgumentNotValidException,这里可以通过自定义异常类来捕捉它,然后去掉BindingResult,以及难看的if判断。

四、自定义异常类捕捉MethodArgumentNotValidException

1
2
3
4
5
6
7
8
9
10
11
@RestControllerAdvice
public class GlobalExceptionAdvice {

@ExceptionHandler(value = MethodArgumentNotValidException.class)
public JsonData validException(MethodArgumentNotValidException e) {
//验证post请求的参数合法性
MethodArgumentNotValidException notValidException = e;
String msg = notValidException.getBindingResult().getFieldError().getDefaultMessage();
return JsonData.buildError(msg);
}
}

使用PostMan的测试结果如下:
image.png

具体的代码可以在我的github上面查看,https://github.com/ruiyeclub/SpringBoot-Hello


12345

  •   GitHub
  •   Ray4j.top
  •   Springboot-Hello
  •    Search
© 2022 — 2025 Cr.    |   
UV PV
TOP