开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Spring IOC: 统一资源加载策略 统一资源 Reso

发表于 2021-11-27

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」。

统一资源 Resource

Resource是Spring框架所有资源的抽象和访问接口,定义了一些通用方法,通过子类AbstractResource提供统一实现

子类结构

image.png

  • FileSystemResource:对File类型资源进行封装
  • ByteArrayResource : 对字节数组提供的数据进行封装。
  • UrlResource:对URL类型资源进行封装
  • ClassPathResource:对class path类型资源的实现
  • InputStreamResource : 将给定的InputStream作为一种资源的Resource实现类

AbstractResource

Resource接口默认实现,实现了Resource接口的大部分公共实现

统一资源定位 ResourceLoader

Spring将资源定义和资源加载区分开,Resource定义了统一的资源,资源的加载由ResourceLoader来统一定义。

ResourceLoader是资源加载的统一抽象,具体资源加载由子类实现,可以将ResourceLoader称为统一资源定位器,主要应用于根据给定资源文件,返回对应的Resource.

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码
public interface ResourceLoader {

String CLASSPATH_URL_PREFIX = "classpath:";

Resource getResource(String var1);

@Nullable

ClassLoader getClassLoader();

}
  1. getResource

根据所提供资源路径location返回Resource实例,该方法支持url,classpath,相对路径等资源

  1. getClassLoader

返回classLoader实例。

子类结构

image.png

DefaultResourceLoader

与AbstractResource相似,是ResourceLoader的默认实现

构造函数

它接收ClassLoader作为构造函数的参数,不带参数的构造函数,使用默认的ClassLoader

getSource方法

getSource方法是ResourceLoader最核心的方法,DefaultResourceLoader对该方法提供了核心的实现。

这个方法首先会根据提供的location返回相应的Resource.

  • 首先通过 ProtocolResolver 来加载资源,成功返回 Resource.
  • 其次会判断llocation的具体类型返回相应的Resource资源。

ProtocolResolver

用户自定义协议资源解决策略,它允许用户自定义资源加载协议,而不需要基础ResourceLoader的子类。

ProtocolResolver 接口,仅有一个方法 Resource resolve(String location, ResourceLoader resourceLoader)。

资源加载示例

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
java复制代码
public class ResourceTest {

public static void main(String[] args) {

ResourceLoader resourceLoader = new DefaultResourceLoader();

Resource fileResource1 = resourceLoader.getResource("E:/k.txt");

System.out.println("fileResource1 is FileSystemResource:" + (fileResource1 instanceof FileSystemResource));

Resource fileResource2 = resourceLoader.getResource("/Users/chenming673/Documents/spark.txt");

System.out.println("fileResource2 is ClassPathResource:" + (fileResource2 instanceof ClassPathResource));

Resource urlResource1 = resourceLoader.getResource("file:/Users/chenming673/Documents/spark.txt");

System.out.println("urlResource1 is UrlResource:" + (urlResource1 instanceof UrlResource));

Resource urlResource2 = resourceLoader.getResource("http://www.baidu.com");

System.out.println("urlResource1 is urlResource:" + (urlResource2 instanceof UrlResource));

}

}

---- 运行结果--

fileResource1 is FileSystemResource:false

fileResource2 is ClassPathResource:true

urlResource1 is UrlResource:true

urlResource1 is urlResource:true

对于fileResource1,之所以返回false,因为它是ClassPathResource类型

我们知道 “E:/k.txt”” 地址,其实在该方法中没有相应的资源类型,那么它就会在抛出 MalformedURLException 异常时,通过 DefaultResourceLoader#getResourceByPath(…) 方法,构造一个 ClassPathResource 类型的资源。

FileSystemResourceLoader

继承了DefaultResourceLoader ,且覆写了 getResourceByPath(String) 方法

用来加载文件系统资源,并返回FileSystemResource类型.

FileSystemContextResource ,为 FileSystemResourceLoader 的内部类,它继承 FileSystemResource 类,实现 ContextResource 接口

ClassRelativeResourceLoader

与FileSystemResourceLoader类似,重写了getResourceByPath()方法,不过它可以根据给定的class所在包或者所在包子包加载资源。

ResourcePatternResolver

它支持根据指定的资源路径匹配模式每次返回多个Resource实例。

总结

  • Spring 提供了 Resource 和 ResourceLoader 来统一抽象整个资源及其定位。使得资源与资源的定位有了一个更加清晰的界限,并且提供了合适的 Default 类,使得自定义实现更加方便和清晰。
  • AbstractResource 为 Resource 的默认抽象实现,它对 Resource 接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可,同时对于自定义的 Resource 我们也是继承该类。
  • DefaultResourceLoader 同样也是 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。
  • DefaultResourceLoader 每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver ,该接口提供了根据指定的 locationPattern 返回多个资源的策略。其子类 PathMatchingResourcePatternResolver 是一个集大成者的 ResourceLoader ,因为它即实现了 Resource getResource(String location) 方法,也实现了 Resource[] getResources(String locationPattern)方法。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PyQt5学习笔记6-10 PyQt5的第六课 - PyQt

发表于 2021-11-27

PyQt5的第六课 - PyQt5类的封装

知识回顾

  • 掌握纯代码写pyqt5程序
  • 显示提示框tooltip功能(这个玩意在所有的控件中都是存在的)

代码封装思路

  • 分析哪些代码需要封装:需要封装的就是我们对窗体配置的代码
  • 利用类的继承特性
  • 调用父类Qwidget的构造方法super().__init__()
  • 调用自己的构造方法

用类的方式去实现空的窗口的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
python复制代码import sys

from PyQt5.QtWidgets import QWidget,QApplication



class MyClass(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle("刘金玉编程")
self.setGeometry(30,40,300,200)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
c = MyClass()
app.exec_()

GUI位置大小函数setGeometry

  • 这个函数相当于resize函数和remove函数功能的合体
  • 函数使用格式:
    控件对象.setGeometry(X轴,Y轴,宽度,高度)

信号

  • 窗体上的信号被app.exec_()死循环监听着.
  • 信号至少在QT中针对某个发生的时间的说法
  • 槽是QT中发生的具体的某个时间,就是槽
  • 窗体上的对GUI界面操作行为都是被监听着的
  • GUI是被事件驱动的

信号槽的简单使用

  • 格式:信号源.信号.connect(槽)
  • 格式解释:信号源(按钮)信号(clicked).connet(某个事件方法)
  • 注意:在绑定信号槽的过程汇总,我们的槽方法可以不加括号的,传递的是方法的引用本身
    说白了,我要的是函数,而不是函数的返回值,这里如果想要加上参数,就用lambda 吧!

加入了按钮的点击关闭时间的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
python复制代码import sys

from PyQt5.QtWidgets import QWidget, QApplication, QPushButton


class MyClass(QWidget):
def __init__(self):
super().__init__()
self.initUI()

def initUI(self):
self.setWindowTitle("刘金玉编程")
self.setGeometry(30,40,300,200)
btn = QPushButton("关闭",self)
btn.move(5,6)
# 将信号发送到一个槽
# 设置按钮的点击后关闭窗体的时间
btn.clicked.connect(self.close)
self.show()

app = QApplication(sys.argv)
c = MyClass()
app.exec_()

总结强调

  1. 掌握类的继承QWidget的基本思想
  2. 掌握类的功能封装
  3. 掌握setGeometry函数
  4. 掌握简单的信号槽的使用

PyQt5的第七课 - PyQt5消息盒子QMessageBox

知识回顾

  • 使用类进行pyqt5的GUI开发封装
  • 使用setGeometry哈数:大小,位置
  • 掌握类的继承与内部屌用

消息盒子

  • 使用类库QMessageBox
  • 不同的图标的消息类别:
  • 带有图标的消息盒子setGeometry,图标可以是问号question,信息information

-注意:使用消息盒子后,最后会返回一个按钮类型的结果,最后这个结果是我们人机交互的结果

点击按钮出现消息选择框,处理消息选择框的结果
基础类封装的代码

重写关闭事件-思路

  • 当点击关闭按钮的时候,执行的是窗体的关闭,而窗体的关闭,等同于点击窗口的右上角的按钮的效果.这个效果是QWidget基类所实现的

-提问:我么该如何实现我们自己需要的关闭时候的效果呢?

  • 重写父类方法
  • 重新写关闭事件

QMessageBox使用格式

使用格式:
QMessageBox.question(self,”提示title”,”消息内容”,”默认选中值”)

如何阻止事件的关闭

  • 利用传过来的时间对象
  • 我们通过输出形式,看到时间对象的属性和方法,猜测到具体的方法
1
2
python复制代码Event.accept()#同意关闭
Event.ignore()#忽略操作

总结强调

  • 掌握的类的封装基本代码

PyQt5的第八课 - PyQt5窗体居中和布局

知识回顾

  1. 窗体事件的重写,closeEvent事件
  2. QMessageBox消息框的使用

窗体的居中

  • 默认情况下,如果没有添加窗体位置的函数,
  • 手动调整到屏幕居中位置
  • 通过desktop()函数阿里获取桌面控件的对象QDesktopWidget
  • 通过桌面对象的width()函数来获取屏幕的宽度的分辨率

标签文本控件

  • 使用类库Qlabel
  • 使用格式:
    lbl = QLabel(“编程创造城市”,self)

绝对布局

  • 直接通过move()到某个像素点的位置
  • 特点:非常灵活
  • 弱点:不能随着窗体的大小而变化

总结强调

  1. 掌握窗体居中布局的方法
  2. 掌握利用绝对布局的方法以及优劣势

PyQt5的第久课 - PyQt5窗体绝对布局和相对布局

知识回顾

  1. 点到了窗体的绝对布局
  2. 窗体的居中方式:根据已知像素,计算窗体的起点位置
  • Desktop()函数,这个函数是在QApplication类中的.函数返回的结果QDesktopWidget对象
  1. 标签控件的使用
  • 想要获取水平方向,调用width函数
  • 想要获取垂直方向,调用height函数

相对布局

  1. 绝对布局是直接将控件载入到窗体的位置就可以了.一般直接采用move函数移动到指定的位置后不变.
  2. 相对布局是要将控件放到盒布局中.一般是还要加入一个盒布局.QHboxlayout(水平方向) QVboxlayout(垂直方向),网格布局(QgridBoxla yout)

绝对布局与相对布局的不同

  • 布局中的控件可以随着窗体的变化而变化
  • 布局中的控件之间的距离可以按照比例来调节

QHBoxLayout

  • 要是水平盒布局不好记的话,那你看这个H是不是,中间有一个横的
  • 把所有的控件只能在水平反向上面排列,会自动一个一个排列不会重叠,这个是一个特性
  • 默认情况下,垂直居中的
  • 记忆方式:看H中的横线,就认为是水平布局

垂直布局的思想

  • 增加弹簧

弹簧

  • 就是直接使用盒布局的addstretcfh方法就可以了
  • Addstretch(弹簧比例)
  • 弹簧比例:是指在窗体中进行指定的比例分割.

总结强调

  1. 掌握相对布局与绝对布局的区别
  2. 掌握兼顾地布局中的水平盒布局与垂直盒布局
  3. 掌握和布局中的控件比例排布关系

PyQt5的第时课 - PyQt5网格布局QGridLayout

知识回顾

  • 掌握QHboxLayout水平盒子布局
  • 掌握QVboxLayout垂直盒子布局
  • 盒子布局,我们可以结合自带的”弹簧功能”addstrach
  • LineEdit类库作用:单行文本框

网格布局

0,0 0,1
1,0 1,1
2,0 2,1
1
2
3
4
python复制代码网格布局的使用时需要注意:
1. 要导入类库QGridLayout
2. 该布局的索引默认从0开始
3. 使用网格布局前先要进行类的实例化

网格布局设置

  • 我们其实可以通过网格布局实例化后的对象,直接通过代码提示的方式看到很多我们可以直接实现的方法.
  • 比如:我们想要设置网格之间的空间距离,我们呢可以设置setSpacing来设置,
  • 举例:Grid.setSpacing(空间的像素值)
  • 我们要学会举一反三,通过set的方式设置其他功能

多行文本框的

  • 使用QTextEdit这个类库
  • 使用方法类似于直接对类的实例化

总结强调

  • 掌握网格布局的思想,QGridLayout
  • 掌握新控件多行文本框的使用
  • 根据QTDesigner来了解新控件,或者根据pyqt5中提供目录来了解新控件

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

暴力解说之首次部署NGINX 前言 下载 安装 配置 官网文

发表于 2021-11-27

前言

本章基于Centos 7.x系统讲解

本章讲解下在项目上线部署的时候对NGINX的操作。有些童鞋在网上百度类似LNMP安装就跟着命令一条一条执行了,如果没报错还好,一旦报错就懵逼状态了。这是对自己、对代码的不负责任的表现。本章带大家”正经”的安装一次NGINX。

下载

下载NGINX的源码包切勿随意查找,好好的NGINX官网在那摆着,何必盲目搜寻呢?

源码包下载地址 : nginx.org/en/download…

image.png

  • Mainline Version 主线版本,也是开发版本测试版本,跟自己没仇的最好别下载
  • Stable version 稳定版本,一般下载就在稳定版本内找就可以了
  • Legacy versions 以往的版本

实际对版本没有太多要求的,下载稳定版本的就可以,每个版本都分.gz的源码包和.exe的windows安装包,会下载游戏还不会下载个压缩包了吗?

很多人纠结安装包放哪比较好,现在告诉你

1
bash复制代码/usr/src

usr 历史上全称是user(用户目录),只不过现在不是这个意思了,。总之放这地死不了人。

1
arduino复制代码wget http://nginx.org/download/nginx-1.14.0.tar.gz

wget 比吃饭还简单的命令了解一下?

image.png

然后就开始下载了,下载懂不懂?没下载过游戏吗?

安装

下载完之后 /usr/src 目录下就有个 nginx-1.14.0.tar.gz 的压缩包,然后就是刷刷刷的命令,撸起袖子就是干

解压缩

tar 解压缩命令

参数 说明
-z 专门解压gzip压缩的,没看到压缩包最后是.gz嘛
-x 解压
-v 解压过程,就是解出来啥文件
-f 指向文件,一定得放最后
1
复制代码tar -zxvf nginx-1.14.0.tar.gz

随后就是咔咔咔的解压,然后出来一些看不懂的文件

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
bash复制代码nginx-1.14.0/
nginx-1.14.0/auto/
nginx-1.14.0/conf/
nginx-1.14.0/contrib/
nginx-1.14.0/src/
nginx-1.14.0/configure
nginx-1.14.0/LICENSE
nginx-1.14.0/README
nginx-1.14.0/html/
nginx-1.14.0/man/
nginx-1.14.0/CHANGES.ru
nginx-1.14.0/CHANGES
nginx-1.14.0/man/nginx.8
nginx-1.14.0/html/50x.html
nginx-1.14.0/html/index.html
nginx-1.14.0/src/core/
nginx-1.14.0/src/event/
nginx-1.14.0/src/http/
nginx-1.14.0/src/mail/
nginx-1.14.0/src/misc/
nginx-1.14.0/src/os/
nginx-1.14.0/src/stream/
nginx-1.14.0/src/stream/ngx_stream_geo_module.c
nginx-1.14.0/src/stream/ngx_stream.c
nginx-1.14.0/src/stream/ngx_stream.h
nginx-1.14.0/src/stream/ngx_stream_limit_conn_module.c
nginx-1.14.0/src/stream/ngx_stream_access_module.c
......

这个时候你所在的 /usr/src 目录下就有了一个 nginx-1.14.0 文件夹

1
复制代码nginx-1.14.0  nginx-1.14.0.tar.gz

./configure

目录有了现在就是安装了,不要百度不要谷歌,官网文档写的那么清楚干那多余的活有什么用。小学英语就能看懂。nginx.org/en/docs/con… , 滑动到最下面,官网给出了一个demo

1
2
3
4
5
6
7
ini复制代码./configure
--sbin-path=/usr/local/nginx/nginx
--conf-path=/usr/local/nginx/nginx.conf
--pid-path=/usr/local/nginx/nginx.pid
--with-http_ssl_module
--with-pcre=../pcre-8.41
--with-zlib=../zlib-1.2.11

对头就是这样安装,如果你不愿了解这些配置,完全就可以

1
bash复制代码./configure

对没错,啥都不用加,9个字母结束战斗,为什么不需要加参数?是因为nginx很多参数都有默认值。

参数 默认 注释
–prefix=path /usr/local/nginx nginx安装路径
–sbin-path=path prefix/sbin/nginx nginx命令路径
–modules-path=path prefix/modules nginx模块路径
–conf-path=path prefix/conf/nginx.conf nginx配置文件路径
–error-log-path=path prefix/logs/error.log nginx默认日志目录

执行./configure

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
rust复制代码checking for OS
+ Linux 3.10.0-514.26.2.el7.x86_64 x86_64
checking for C compiler ... found
+ using GNU C compiler
+ gcc version: 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
checking for gcc -pipe switch ... found
checking for -Wl,-E switch ... found
checking for gcc builtin atomic operations ... found
checking for C99 variadic macros ... found
checking for gcc variadic macros ... found
checking for gcc builtin 64 bit byteswap ... found
checking for unistd.h ... found
checking for inttypes.h ... found
checking for limits.h ... found
checking for sys/filio.h ... not found
checking for sys/param.h ... found
checking for sys/mount.h ... found
checking for sys/statvfs.h ... found
checking for crypt.h ... found
checking for Linux specific features
checking for epoll ... found
checking for EPOLLRDHUP ... found
checking for EPOLLEXCLUSIVE ... not found
checking for O_PATH ... found
checking for sendfile() ... found
checking for sendfile64() ... found
....

configure是个检查工具,上面的直接结果很清楚了,各种的checking,检测下环境支持不?依赖都装了吗?检测成功后你才可以进入下一步,否则你进入下一步也依旧会提示xxx不存在,xxx不支持等等

1
go复制代码make && make install

make是一个编译工具,你就记住是个编译工具就行了,编译的过程输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
css复制代码cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs \
-o objs/src/core/nginx.o \
src/core/nginx.c
cc -c -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs \
-o objs/src/core/ngx_log.o \
src/core/ngx_log.c
cc -c -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs \
-o objs/src/core/ngx_palloc.o \
src/core/ngx_palloc.c
cc -c -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs \
-o objs/src/core/ngx_array.o \
src/core/ngx_array.c
....
cp conf/nginx.conf '/usr/local/nginx/conf/nginx.conf.default'
test -d '/usr/local/nginx/logs' \
|| mkdir -p '/usr/local/nginx/logs'
test -d '/usr/local/nginx/logs' \
|| mkdir -p '/usr/local/nginx/logs'
test -d '/usr/local/nginx/html' \
|| cp -R html '/usr/local/nginx'
test -d '/usr/local/nginx/logs' \
|| mkdir -p '/usr/local/nginx/logs'
make[1]: 离开目录“/usr/src/nginx-1.14.0”

最后他copy了一些必须文件到指定目录里,这个时候nginx的安装就基本完成了。

配置

如果是仅仅执行了这条命令

1
bash复制代码./configure

那nginx的目录就是

1
2
3
markdown复制代码/usr/local/nginx
-----------
cert client_body_temp conf fastcgi_temp html logs proxy_temp sbin scgi_temp uwsgi_temp

以下讲解的所有配置都在nginx.conf内进行

修改默认指向目录

取消默认指向的解析目录html 直接注释掉就行了。一般我会把项目目录指向 /var/www 一般都在虚拟主机文件中指向

避免泛解析

总有些人会把域名解析错地址或者是恶意解析到你的服务器上。对于这种人必须严惩

1
2
3
4
5
ini复制代码server {
listen 80 default_server;
server_name _;
return 403;
}

分分钟屏蔽他

添加虚拟主机

虚拟主机的配置文件可千万别都写到nginx.conf中
一般我会在nginx.conf同级建立一个server目录存放

1
bash复制代码include /usr/local/nginx/conf/server/*.conf;

配置Gzip压缩

nginx.org/en/docs/htt…

添加SSL支持

segmentfault.com/a/119000001…

开启日志记录

segmentfault.com/a/119000001…

开启负载均衡

segmentfault.com/a/119000001…

开启反向代理

segmentfault.com/a/119000001…

设置权限

最好设置nginx命令之允许root用户或者你们公司的运营执行,无缘无故的nginx -s stop可受不了。

官网文档

nginx官网是我感觉文档写的最简介最详细的文档。以下列出各部分详细地址

  • 编译配置参数 nginx.org/en/docs/con…
  • 虚拟主机配置 nginx.org/en/docs/htt…
  • 配置文件中的计量单位 nginx.org/en/docs/syn…
  • nginx命令参数 nginx.org/en/docs/swi…
  • nginx负载均衡配置 nginx.org/en/docs/htt…
  • nginx官方博客 www.nginx.com/blog/

致谢

感谢你看到这里,本篇文章我的语言过于偏激了,还希望见谅。希望本篇文章可以帮助到你,有什么问题可以在评论区留言。谢谢

** 别害怕英语,小学英语水平就能看懂,一切源于坚持 **

交流

生命不息,编码不止。

微信搜索 【一文秒懂】 传播技术正能量,持续学习新知识。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

电商系统设计之商品【番外篇】 前言 运费模版 商家相关 商品

发表于 2021-11-27

前言

这是电商系统设计系列在商品设计这块的最后一篇文章。以下是其他文章地址,按照逻辑顺序排列如下

  • 电商系统设计之用户系统 segmentfault.com/a/119000001…
  • 电商系统设计之购物车 segmentfault.com/a/119000001…
  • 电商系统设计之商品 (上) segmentfault.com/a/119000001…
  • 电商系统设计之商品 (中) segmentfault.com/a/119000001…
  • 电商系统设计之商品 (下) segmentfault.com/a/119000001…
  • 电商系统设计之订单 segmentfault.com/a/119000001…
  • 电商系统设计之商品接口 segmentfault.com/a/119000001…

在以上文章中,有些地方描述的不够全面,这篇文章就当补个漏了。

运费模版

运费模版的设计一般依照淘宝的设计来就比较完美了。

配置 说明
模版名称 用于商家选择模版
所在地区/详细地址 主要是标记货源在什么位置
发货时间 承诺多长时间可以发货
是否包邮 很多买家都比较关心这个
运费 如果不包邮,运费是多少
件数/重量/体积 这里与运费相关,具体可看下方配图
指定条件包邮 某些地区包邮,例如本市或者相邻的市等等

image.png

商家相关

配置 说明
商家名称 显示在商品详情页等等
商家地址 用于匹配用户所在地区
详细地址 用于记录
所属行业 用于匹配

如果是一家创业电商公司并且是一个多商户的电商,那你至少需要收集以下商户的相关信息

  • 法人代表姓名
  • 联系方式
  • 代理商公司
  • 代理商公司地址
  • 代理商公司法人
  • 公司邮箱
  • 法人身份证正反面
  • 营业执照
  • 开户行相关信息
  • 其他资质(授权书,资格证明等等)

商品配置

配置 说明
收藏数 商品收藏数只需记录用户点击收藏即可,取消收藏收藏数其实并不需要减少 (自行理解,不能多说)
分享数 每次分享都需要记录
上架设置 立即上架、定时上架还是不上架
是否推荐 推荐到指定搜索关键字或者是首页类目等等

其他配置

配置 说明
商品关键字(商品属性) 手动填写,方便检索
虚拟销量 前期总得有点假数据的嘛
是否允许退换货 有些商品是可能无法退换货的,类似与互联网文档、源码等
减库存方式(拍下减、付款减、不减) 有些商品是无库存限制的,类似于互联网文档、源码等
购买权限(单次最低购买数、一定时间段内最多购买数) 有些商品是限购的或者某段时间只能购买指定数量
是否包邮、包邮条件 这项并非单独的配置,一般在运费模版内设置
商品付款通知 商品购买是否通知,如何通知,通知到哪里
库存提醒 库存降低到指定数量提交商家补货
自动下架 库存降低到指定数量自动下架

致谢

感谢你看到这里。

电商系统商品相关的文章已经到了尾声,如果有其他商品相关的文章需要编写,可以私信联系我,毕竟我也是公司员工,写这些文章并不是我的工作,只是记录我的职业生涯。当然我也希望可以帮助到各位。例如优惠券、活动(限时优惠)等等将在其他章节为大家呈现,谢谢。

交流

生命不息,编码不止。

微信搜索 【一文秒懂】 传播技术正能量,持续学习新知识。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

电商系统设计之商品接口 前言 接口设计 接口文档 致谢 交流

发表于 2021-11-27

前言

我应该是少数在文章中直接展示接口文档的人。本篇我思考了很久到底要不要解析下商品接口开发的注意点。

客户端开发与服务端开发即是天敌也是兄弟。希望本篇文章让你们减少争执,把“爱”给对方。

接口设计

简述

电商系统设计之中,比较复杂的接口就论商品详情的接口了,响应参数特别多,特别杂。在开发获取商品详情接口时要遵循以下几个原则

  • 返回的JSON嵌套数量要少
  • 方便去查询到指定的SKU
  • 其他接口相关规范

image.png

查询SKU

关于查询SKU,我让我的小伙伴是这样做的,首先拿出规格和属性

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
json复制代码"选择颜色": [
{
"name": "银色",
"id": 75
}
],
"选择版本": [
{
"name": "公开版",
"id": 77
},
{
"name": "【原厂延保版】",
"id": 78
}
],
"内存": [
{
"name": "64G",
"id": 82
},
{
"name": "256G",
"id": 83
}
],

没错,你没有看错,实际就是将规格作为key,属性作为value。将value[id]取出,进行拼接即可查询到对应的SKU了。

1
css复制代码响应参数[规格名称][属性编码] = 拼接SKU串的必需品

规格相当于一个分组,属性其实也是拼接SKU的重要组成部分,上述数据为例

1
ini复制代码75_77_82 = 银色,公开版,64G

接口文档

请求地址

/v1/product/{productId}

请求类型

GET

请求参数

参数 类型 默认值 说明
productId int 0 商品编码

响应示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
css复制代码{
"code": 200,
"message": "获取成功",
"data": {
"id": 131,
"name": "Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机",
"price": "8388.00",
"market_price": "8388.00",
"sketch": "IPhone大法好,打九折,打九折,快剁手",
"intro": "这是商品描述",
"keywords":['苹果','iphone'],
"attribute": {
"选择颜色": [
{
"name": "银色",
"id": 75
},
{
"name": "深空灰色",
"id": 76
}
],
"选择版本": [
{
"name": "公开版",
"id": 77
},
{
"name": "【原厂延保版】",
"id": 78
},
{
"name": "双网通版",
"id": 79
},
{
"name": "无线充套装",
"id": 80
},
{
"name": "Airpods套装",
"id": 81
}
],
"内存": [
{
"name": "64G",
"id": 82
},
{
"name": "256G",
"id": 83
}
],
"购买方式": [
{
"name": "官方标配",
"id": 84
},
{
"name": "移动优惠购",
"id": 85
},
{
"name": "电信优惠购",
"id": 86
},
{
"name": "联通优惠购",
"id": 87
}
]
},
"album": [
{
"id": 2,
"name": "这是第一张图片",
"url": "http://xxx.com/59ec33eaN6ddb0c54.jpg"
},
{
"id": 3,
"name": "这是第二张图片",
"url": "http://xxx.com/59ec3400Nce4cc116.jpg"
}
],
"radio": {
"id": 1,
"name": "这是一个视频",
"url": "http://xxx.com/1.mp4"
},
"sku": {
"75_77_82_84": {
"id": 1018,
"name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:官方标配;",
"price": "8388.00",
"stock": 83888388
},
"75_77_82_85": {
"id": 1019,
"name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:移动优惠购;",
"price": "8388.00",
"stock": 83888388
},
"75_77_82_86": {
"id": 1020,
"name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:电信优惠购;",
"price": "8388.00",
"stock": 83888388
},
"75_77_82_87": {
"id": 1021,
"name": "选择颜色:银色;选择版本:公开版;内存:64G;购买方式:联通优惠购;",
"price": "8388.00",
"stock": 83888388
},
"75_77_83_84": {
"id": 1022,
"name": "选择颜色:银色;选择版本:公开版;内存:256G;购买方式:官方标配;",
"price": "8388.00",
"stock": 83888388
},
"75_77_83_85": {
"id": 1023,
"name": "选择颜色:银色;选择版本:公开版;内存:256G;购买方式:移动优惠购;",
"price": "8388.00",
"stock": 83888388
}
}
}
}

响应参数说明

核心参数

参数 类型 默认值 说明
id int 0 商品编码
name string - 商品标题
price double 00.00 商品价格
keywords string - 商品关键字
market_price double 00.00 市场价格
virtual int 0 虚拟销量
sketch string - 商品简述
intro string - 商品详情

商品图参数

参数 类型 默认值 说明
album[] array [] 商品轮播图
id int 0 资源编码
name string - 图片名称
url string - 资源路径

商品视频参数

无视频则返回 []

参数 类型 默认值 说明
radio[] array [] 商品视频
id int 0 资源编码
name string - 视频名称
url string - 资源路径

商品规格/属性参数

参数 类型 默认值 说明
attribute array[] [] 商品属性
[(attr_name)] [] array[] [] 属性名称
name string - 属性项名称
id int 0 属性项编码

商品SKU参数

参数 类型 默认值 说明
sku[] array[] [] 商品sku
[(option_id)] [] array[] 商品SKU查询办法为 attribute[(attr_name)][‘id’] 拼接
id int 0 sku编码
name string - sku 名称
price double 00.00 商品价格
stock int 0 商品库存

致谢

字不在多,讲清楚就行,感谢你看到这里,希望本篇文章可以帮助到你,有疑问可以在评论区讨论,谢谢。

交流

生命不息,编码不止。

微信搜索 【一文秒懂】 传播技术正能量,持续学习新知识。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【k8s 系列】k8s 学习十一,Label,RC,HPA

发表于 2021-11-27

「这是我参与11月更文挑战的第 26 天,活动详情查看:2021最后一次更文挑战」

上面简单说了一下 pod 的基本知识点,待到后面会使用到 pod 的一些高阶知识点的时候,还可以再细细琢磨底层原理

我们接着继续学习 Lable , RC,HPA 的相关知识点

Label 是什么?

label 就是标签,例如之前我们手写的 yaml 文件中,每一个键值对都是 label,如 key =value

label 一般是在定义资源的时候就确定了,当然我们也可以在对象创建之后进行动态的添加,编辑,和删除

我们写的 label 可以附加到 Node,Service,Pod,RC 中,每一类资源都可以定义任意数量的 label, 同一个 label 也是可以被添加到任意数量的资源对象上的

这一点确实是很灵活了有么有

label 为什么要附加到各种资源对象上呢?

因为 label 和 label selector 都是不能单独定义的,他们必须要依附在某一类资源对象上,这是为了能够去管理这些资源,使用 label selector 对他们分组

例如写一个 openresty ,写一个 RC 使用 openresty ,Service 调用 openresty 的 label selector

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
yaml复制代码apiVersion: v1
kind: ReplicationController
metadata:
name: openresty
spec:
replicas: 2
selector:
app: openresty
tmplate:
metadata:
labels:
app: openresty
spec:
containers:
- name: openresty
ports:
- containerPort: 80
----------------------------
apiVersion: v1
kind: Service
metadata:
name: openresty
spec:
type: NodePort
ports:
- port: 80
nodePort: 30125
selector:
app: openresty

RC 是什么?

RC 是什么,前面解释到,就是 ReplicationController 副本控制器

当我们定义一个 RC ,提交给 K8S 的时候,这是一个期望值,Master 节点上的 Controller Manager 组件得到通知后,就会去定期检查 pod ,会确保 pod 的数量和我们预期的数量一致

如果当前检测到的数量和我们的预期数量不一致,那么控制器就会对 pod 做增加和删除的操作,这就可以实现 pod 动态扩缩功能

之前我们也有提到,我们可以使用如下指令来设置我们期望的副本数

1
shell复制代码kubectl scale rc 服务名 --replicas=具体的数字

RC 和 Replica Sets 有何区别?

他俩在本质上没有什么区别,只是 Replica Sets 支持的更多

RC Replica Sets
支持基于等式的 label selector 支持基于集合的 label selector
K8S 1.2 之后出现的,Replica Sets 主要是被 Deployment 这样的资源对象所使用

为了提高我们应用的容灾能力,哪怕程序只有 1 个 pod 副本,也要通过 RC 的方式去管理,因为 RC 可以管理 pod 的创建,删除,更新等编排机制,不用 pod 自己瞎操心

HPA 是什么?

HPA 就是 Horizontal Pod Autoscal ,pod 的横向扩容,他也是 K8S 的一种资源对象

HPA 的简单原理

HAP 通过追踪和分析 RC 的 pod 的负载变化情况,酌情调整目标 pod 的副本数

K8S 提供 2 种方式来对 pod 扩容和缩容

  • 第一种是我们使用命令的方式对 RC,Deployment 进行扩容和缩容
1
shell复制代码kubectl scale rc/deployment 服务名 --replicas 数量
  • 第二种是使用 HPA 的方式,自动扩容和缩容

自动的模式就**需要用户根据指定的一个性能指标,还要预先指定一个副本的数量范围,**系统就会自动的在我们设定的范围内根据性能指标进行调整

HPA 小案例

  • 写一个 自己 nginx deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
yaml复制代码apiVersion: extension/v1beta1
kind: Deployment
metadata:
name: my nginx deployment
spec:
replicas: 2
template:
metadata:
name: my deployment
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requrests:
cpu: 50m
ports:
- containerPort: 80
  • 写一个 service

写一个 service ,命名为 mynginx-svc

1
2
3
4
5
6
7
8
9
yaml复制代码apiVersion: v1
kind: Service
metadata:
name: mynginx-svc
spec:
ports:
- port: 80
selector:
app: nginx
  • 写一个 hpa

hpa 设置最小的副本数 2, 最大的副本数 5个,CPU 在 70 % 以下 ,引用 my nginx deployment

1
2
3
4
5
6
7
8
9
10
11
12
yaml复制代码apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: mynginx-hpa
spec:
scaletargetRef:
apiversion: app/v1beta1
kind: deployment
name: my nginx deployment
minReplicas: 2
maxReplicas: 5
targetCPUUtillizationPercentage: 70

今天就到这里,学习所得,若有偏差,还请斧正

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

对象内存布局和对象头

发表于 2021-11-27

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」

一 对象的(堆)内存布局

对象实例 = 对象头+实体数据+对齐填充(保证8个字节的倍数)

【如果new一个对象,但是此时对象14个字节,比8大,比16小两个字节,对齐填充会自动补齐到16】

对象头 = 对象标记Mark Word + 类元信息(又叫类型指针)。

下面图是java Object

array Object

1 对象头

对象标记包含着【哈希码,GC标记,GC次数,同步锁标记,偏向锁持有者】(每个对象都有一个hashcode)

默认存储对象的HashCode、分代年龄和锁标志位等信息。

这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。

它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。

**类元信息(class pointer):**对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

注意:在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。(默认)

2 实例数据

存放类的属性(Field)数据信息,包括父类的属性信息,

如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。

假如类中的属性数据信息的字节数,只有21字节,那么对齐填充就会按4字节对齐,补齐到24。

3 对齐填充

将头和数据的字节数补齐用的。

整个对象的字节数 = 对象头字节数(16 默认) + 实际数据的字节数。

锁状态从上到下,从无锁到重锁,锁升级。

可以使用VM.current().details()查看虚拟机的详细信息。

下面代码可以查看当前对象的字节,头部信息等信息。

1
2
3
typescript复制代码12345

public static void main(String[] args) { Object o = new Object(); //需要引入JOL依赖 System.out.println( ClassLayout.parseInstance(o).toPrintable());}Copy

结果:

前两个header(Mark word 8个字节),第三个header类型指针4个字节

使用上面的代码,如果引入了别的含有数据的类:

1
2
3
arduino复制代码12345

class B{ int i = 25; boolean s = false;}Copy

这时候,实际要在上图的后面加上4字节,再加上boolean的1位字节,所以总数大概有17个字节,但是实际是需要补8倍。

17比24少7,所以补7位到24。

注意:GC年龄采用4位的存储,最大值为15,例如MaxTenuringThreshold参数的默认值就是15.

如果超过16,就会抛异常。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

一文搞懂Nginx代理 写在前面 代理 正向代理 反向代理

发表于 2021-11-27

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

写在前面

Nginx服务器的反向代理服务是其最常用的重要功能,由反向代理服务也可以衍生出很多与此相关的Nginx服务器重要功能,比如后面会介绍的负载均衡。

我们会先介绍Nginx的反向代理,当然在了解反向代理之前,我们需要先知道什么是代理以及什么是正向代理。

代理

在Java设计模式中,代理模式是这样定义的:给某个对象提供一个代理对象,并由代理对象控制原对象的引用。

可能大家不太明白这句话,在举一个现实生活中的例子:比如我们要买一间二手房,虽然我们可以自己去找房源,但是这太花费时间精力了,而且房屋质量检测以及房屋过户等一系列手续也都得我们去办,再说现在这个社会,等我们找到房源,说不定房子都已经涨价了,那么怎么办呢?最简单快捷的方法就是找二手房中介公司(为什么?别人那里房源多啊),于是我们就委托中介公司来给我找合适的房子,以及后续的质量检测过户等操作,我们只需要选好自己想要的房子,然后交钱就行了。

代理简单来说,就是如果我们想做什么,但又不想直接去做,那么这时候就找另外一个人帮我们去做。那么这个例子里面的中介公司就是给我们做代理服务的,我们委托中介公司帮我们找房子。

Nginx主要能够代理如下几种协议,其中用到的最多的就是做Http代理服务器。

image.png

正向代理

弄清楚什么是代理了,那么什么又是正向代理呢?

这里我再举一个例子:大家都知道,现在国内是访问不了Google的,那么怎么才能访问Google呢?我们又想,美国人不是能访问Google吗(这不废话,Google就是美国的),如果我们电脑的对外公网IP地址能变成美国的IP地址,那不就可以访问Google了。你很聪明,VPN就是这样产生的。我们在访问Google时,先连上VPN服务器将我们的IP地址变成美国的IP地址,然后就可以顺利的访问了。

这里的VPN就是做正向代理的。正向代理服务器位于客户端和服务器之间,为了向服务器获取数据,客户端要向代理服务器发送一个请求,并指定目标服务器,代理服务器将目标服务器返回的数据转交给客户端。这里客户端是要进行一些正向代理的设置的。

反向代理

反向代理和正向代理的区别就是:正向代理代理客户端,反向代理代理服务器。

反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。

理解这两种代理的关键在于代理服务器所代理的对象是什么,正向代理代理的是客户端,我们需要在客户端进行一些代理的设置。而反向代理代理的是服务器,作为客户端的我们是无法感知到服务器的真实存在的。

总结起来还是一句话:正向代理代理客户端,反向代理代理服务器。

Nginx反向代理

范例:使用nginx反向代理www.123.com直接跳转到127.0.0.1:8080

①、启动一个tomcat,浏览器地址栏输入127.0.0.1:8080,出现如下界面

image.png

②、通过修改本地host文件,将www.123.com映射到127.0.0.1

127.0.0.1 www.123.com
将上面代码添加到 Windows 的host 文件中,该文件位置在:

image.png

配置完成之后,我们便可以通过www.123.com:8080访问到第一步出现的Tomcat初始界面。

那么如何只需要输入www.123.com便可以跳转到Tomcat初始界面呢?便用到nginx的反向代理。

③、在nginx.conf配置文件中增加如下配置:

image.png

如上配置,我们监听80端口,访问域名为www.123.com,不加端口号时默认为80端口,故访问该域名时会跳转到127.0.0.1:8080路径上。

我们在浏览器端输入www.123.com结果如下:

image.png
④、总结

其实这里更贴切的说是通过nginx代理端口,原先访问的是8080端口,通过nginx代理之后,通过80端口就可以访问了。

Nginx反向代理相关指令介绍

①、listen

该指令用于配置网络监听。主要有如下三种配置语法结构:

一、配置监听的IP地址

1
2
3
ini复制代码listenaddress[:port][default_server][setfib=number][backlog=number]
[rcvbuf=size][sndbuf=size][deferred]
[accept_filter=filter][bind][ssl];

二、配置监听端口

1
2
3
ini复制代码listenport[default_server][setfib=number][backlog=number][rcvbuf=size]
[sndbuf=size][accept_filter=filter]
[deferred][bind][ipv6only=on|off][ssl];

三、配置UNIX Domain Socket

1
2
3
ini复制代码listenunix:path[default_server][backlog=number][rcvbuf=size][sndbuf=size]
[accept_filter=filter]
[deferred][bind][ssl];

上面的配置看似比较复杂,其实使用起来是比较简单的:

1
2
3
4
perl复制代码1  listen*:80|*:8080#监听所有80端口和8080端口
2 listenIP_address:port#监听指定的地址和端口号
3 listenIP_address#监听指定ip地址所有端口
4 listenport#监听该端口的所有IP连接

下面分别解释每个选项的具体含义:

  • 1、address:IP地址,如果是IPV6地址,需要使用中括号[]括起来,比如[fe80::1]等。
  • 2、port:端口号,如果只定义了IP地址,没有定义端口号,那么就使用80端口。
  • 3、path:socket文件路径,如var/run/nginx.sock等。
  • 4、default_server:标识符,将此虚拟主机设置为address:port的默认主机。(在nginx-0.8.21之前使用的是default指令)
  • 5、setfib=number:Nginx-0.8.44中使用这个变量监听socket关联路由表,目前只对FreeBSD起作用,不常用。
  • 6、backlog=number:设置监听函数listen()最多允许多少网络连接同时处于挂起状态,在FreeBSD中默认为-1,其他平台默认为511.
  • 7、rcvbuf=size:设置监听socket接收缓存区大小。
  • 8、sndbuf=size:设置监听socket发送缓存区大小。
  • 9、deferred:标识符,将accept()设置为Deferred模式。
  • 10、accept_filter=filter:设置监听端口对所有请求进行过滤,被过滤的内容不能被接收和处理,本指令只在FreeBSD和NetBSD 5.0+平台下有效。filter可以设置为dataready或httpready。
  • 11、bind:标识符,使用独立的bind()处理此address:port,一般情况下,对于端口相同而IP地址不同的多个连接,Nginx服务器将只使用一个监听指令,并使用bind()处理端口相同的所有连接。
  • 12、ssl:标识符,设置会话连接使用SSL模式进行,此标识符和Nginx服务器提供的HTTPS服务有关。

②、server_name

该指令用于虚拟主机的配置。

通常分为以下两种:

1、基于名称的虚拟主机配置语法格式如下:

1
ini复制代码server_name name...;

一、对于name来说,可以只有一个名称,也可以有多个名称,中间用空格隔开。而每个名字由两段或者三段组成,每段之间用“.”隔开。

1
复制代码server_name 123.com www.123.com

二、可以使用通配符“*”,但通配符只能用在由三段字符组成的首段或者尾端,或者由两端字符组成的尾端。

1
复制代码server_name *.123.com www.123.*

三、还可以使用正则表达式,用“~”作为正则表达式字符串的开始标记。

1
ini复制代码server_name  ~^www\d+\.123\.com$;

该表达式"~"表示匹配正则表达式,以www开头(“^”表示开头),紧跟着一个0~9之间的数字,在紧跟“.123.co”,最后跟着“m”($表示结尾)

以上匹配的顺序优先级如下:

1
2
3
4
复制代码1 ①、准确匹配server_name
2 ②、通配符在开始时匹配server_name成功
3 ③、通配符在结尾时匹配server_name成功
4 ④、正则表达式匹配server_name成功

2、基于IP地址的虚拟主机配置

语法结构和基于域名匹配一样,而且不需要考虑通配符和正则表达式的问题。

1
复制代码server_name 192.168.1.1

③、location

该指令用于匹配URL。

语法如下:

1
2
3
css复制代码1  location[=|~|~*|^~]uri{
2
3 }

1、=:用于不含正则表达式的uri前,要求请求字符串与uri严格匹配,如果匹配成功,就停止继续向下搜索并立即处理该请求。

2、~:用于表示uri包含正则表达式,并且区分大小写。

3、~*:用于表示uri包含正则表达式,并且不区分大小写。

4、^~:用于不含正则表达式的uri前,要求Nginx服务器找到标识uri和请求字符串匹配度最高的location后,立即使用此location处理请求,而不再使用location块中的正则uri和请求字符串做匹配。

注意:如果uri包含正则表达式,则必须要有或者*标识。

④、proxy_pass

该指令用于设置被代理服务器的地址。可以是主机名称、IP地址加端口号的形式。

语法结构如下:

1
ini复制代码proxy_pass URL;

URL为被代理服务器的地址,可以包含传输协议、主机名称或IP地址加端口号,URI等。

1
bash复制代码proxy_pass http://www.123.com/uri;

⑤、index

该指令用于设置网站的默认首页。

语法为:

1
diff复制代码index filename...;

后面的文件名称可以有多个,中间用空格隔开。

1
diff复制代码index index.html index.jsp;

通常该指令有两个作用:第一个是用户在请求访问网站时,请求地址可以不写首页名称;第二个是可以对一个请求,根据请求内容而设置不同的首页。

弦外之音

感谢你的阅读,如果你感觉学到了东西,您可以点赞,关注。也欢迎有问题我们下面评论交流

加油! 我们下期再见!

给大家分享几个我前面写的几篇骚操作

copy对象,这个操作有点骚!

干货!SpringBoot利用监听事件,实现异步操作

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

栈刷题记(一-有效的括号)

发表于 2021-11-27

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

前言

  • 啦啦啦,小嘟俺又回来啦,嘿嘿嘿😂😂😂!,今天小嘟想了一下,准备进军下一个专题,想了想准备刷关于栈方面的题目。
  • 说干就干,今天是第一天,那就做一个简单题,让我们认识认识。

小嘟叨叨时刻

  • 小嘟这段时间一直在做二叉树方面的题目,感觉二叉树的题目的规律性很强。你只要能发现这个题目的规律,代码其实挺简单的。这里的重中之重还是大家要学会三种遍历,小嘟觉得遍历的目的就是遍历所有可能的结果,而我们要做的就是找到我们需要的结果。
  • 总结的不是很准确,想要表达的中心还是希望读者能够熟练的掌握三种基本的遍历方式。
  • 在做题过程中,可能几个小时或者几天才能懂得一道题目,这些都没关系。你要做的就是不断地坚持,总结(你为什么没做出来,我那一部分没想到呢?),然后不断的重复。我们先从简单的题目开始,等基础差不多了,我们就可以进阶做中等、困难,千万不要半途而废。

小嘟大道理

  • 小嘟认为:人和人是有差距的,但是这只是少部分人,我们大多数人智商其实都差不多,那么为什么有人很突出,有人很普通。在小嘟看来,最大的区别在于坚持,你一直坚持好好学习,几天几个月看不出来差距,那么几年几十年呢?这不就出来了!
    小嘟叨叨时刻结束,小嘟真爱叨叨,真的是真爱...

正文

预备知识

  • 既然要学栈,那么我们就要知道,栈是什么?
  • 小嘟用大白话说说:栈结构的特点是先进后出,比如这里有一个箱子,我们在里边放我们的书,如果我们那天想要用最下边的那本书,我们是不是就得把它上边的书都拿出来,这个就体现了先进的后出来的特点,这就是栈。
    看图直观点:

image.png

我们把书放到箱子里,比方说我们要拿出第二本书,那么我们就要把第三本、第四本、第五本书从箱子里拿出来。

这就是栈,先进去的后出来,也就是进去和出来只有一个门,我们都只能经过这个门。

题目

image.png

题目约束条件

image.png

示例

image.png

题目分析

  • 首先,我们知道题目给的是一个字符串。
  • 其次,字符串只包括六种字符,分别是 '(',')','{','}','[',']'。
  • 然后呢,就是有效字符串的定义喽,这个题目部分中有。
  • 再然后我们肯定要遍历字符串的元素喽!
  • 最后返回最终结果(true Or Not true)

代码及其结果展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ini复制代码var isValid = function(s) {
let stack = [];
let i = 0;
if((s.length%2)) return false;
while(i<s.length){
if(s[i] =='(' ||s[i]=='{' || s[i] == "["){
stack.push(s[i]);
i++;
continue;
}
let target = stack.pop();
if((s[i]==')'&&target=='(')||(s[i]=='}'&&target=='{')||(s[i]==']'&&target=='['))
i++;
else{
return false;
}
}
return stack.length == 0?true:false;
};

image.png

本题回顾

  • 小嘟带读者走一遍代码。首先我们分析题目可以知道,如果字符串的长度为奇数,那么它肯定不是有效字符串。
  • 所以我们判断出字符串的长度为奇数,那么就直接返回false。
  • 如果是偶数长度,那么我们就从头遍历整个字符串。
  • 我们要进行的操作如下:
  • 1.如果是左边的括号,那么我们就把它入栈。
  • 2.如果不是左边的括号,我们就从栈中拿一个元素出来比较,如果满足上述的任意条件,那么继续循环,反之,我们可以知道括号不匹配,故它不是有效字符串,返回false
  • 3.循环退出后,我们还需判断栈中是否有元素(例如s = '(((()))'),有的话说明没有出完,那么就不是有效字符串。

结尾

  • 啦啦啦,看到这里,小嘟就要和读者说再见了。每次写文章的时候,小嘟就特别有成就感,感觉这是一件有意义的事情。
  • 有人说自己不会写文章,小嘟也不会写,但是写着写着就会写了,而且写文章的时候你就相当于在给别人讲一遍做题的思路,这可以加深自己对题目的理解。
  • 写文章的时候还可以让自己静下心来,进入深度思考。
  • 总之,没事了谢谢文章蛮好的,每天看到自己的文章有人阅读,我就很开心,因为文章帮助到了有需要的人。
    小嘟玩游戏去啦,嘿嘿嘿,读者,拜拜啦!...

附件

题目直通车 leetcode-cn.com/problems/va…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JWT十分钟拿下! 一、什么是JWT 二、JWT能做什么 三

发表于 2021-11-27

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」。

一、什么是JWT

在这里插入图片描述

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

​ —[摘自官网]

1
2
3
4
5
6
markdown复制代码# 1.翻译
- 官网地址: https://jwt.io/introduction/
- 翻译: jsonwebtoken(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名

# 2.通俗解释
- JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

二、JWT能做什么

1
2
3
4
5
markdown复制代码# 1.授权
- 这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。

# 2.信息交换
- JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。

三、为什么是JWT

3.1、基于传统的Session认证

1
2
3
4
markdown复制代码# 1.认证方式
- 我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
t
# 2.认证流程

在这里插入图片描述

1
2
3
4
5
6
7
8
9
markdown复制代码# 3.暴露问题
- 1.每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大

- 2.用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

- 3.因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

- 4.在前后端分离系统中就更加痛苦:如下图所示
也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session 每次携带sessionid 到服务 器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF(跨站伪造请求攻 击)攻击,session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是 sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。 不方便集群应用。

在这里插入图片描述

3.2、基于JWT认证

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
markdown复制代码# 1.认证流程
- 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
- 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.zzz.xxx的字符串。 token head.payload.singurater

- 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。

- 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) HEADER

- 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
- 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

# 2.jwt优势

- 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快

- 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

- 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。

- 不需要在服务端保存会话信息,特别适用于分布式微服务。

四、JWT的结构是什么?

1
2
3
4
5
6
markdown复制代码token   string  ====>  header.payload.singnature 
# 1.令牌组成
- 1.标头(Header)
- 2.有效载荷(Payload)
- 3.签名(Signature)
- 因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz Header.Payload.Signature
1
2
3
4
markdown复制代码# 2.Header
- 标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。

- 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
1
2
3
4
json复制代码{
"alg": "HS256",
"typ": "JWT"
}
1
2
markdown复制代码# 3.Payload
- 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分
1
2
3
4
5
json复制代码{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
1
2
3
4
5
6
7
8
9
10
11
12
markdown复制代码# 4.Signature
- 前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过
- 如:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret);

# 签名目的
- 最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

# 信息安全问题
- 在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

- 是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

在这里插入图片描述

1
2
3
4
5
6
markdown复制代码# 5.放在一起
- 输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑。
- 简洁(Compact)
可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
- 自包含(Self-contained)
负载中包含了所有用户所需要的信息,避免了多次查询数据库

在这里插入图片描述

五、使用JWT

1
markdown复制代码# 1.引入依赖
1
2
3
4
5
6
xml复制代码<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
1
markdown复制代码# 2.生成token
1
2
3
4
5
6
7
8
9
java复制代码Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 90);
//生成令牌
String token = JWT.create()
.withClaim("username", "张三")//设置自定义用户名
.withExpiresAt(instance.getTime())//设置过期时间
.sign(Algorithm.HMAC256("token!Q2W#E$RW"));//设置签名 保密 复杂
//输出令牌
System.out.println(token);
1
2
markdown复制代码- 生成结果
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5byg5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg
1
markdown复制代码# 3.根据令牌和签名解析数据
1
2
3
4
java复制代码JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户名: " + decodedJWT.getClaim("username").asString());
System.out.println("过期时间: "+decodedJWT.getExpiresAt());
1
2
3
4
5
markdown复制代码# 4.常见异常信息
- SignatureVerificationException: 签名不一致异常
- TokenExpiredException: 令牌过期异常
- AlgorithmMismatchException: 算法不匹配异常
- InvalidClaimException: 失效的payload异常

在这里插入图片描述

六、封装工具类

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
java复制代码public class JWTUtils {
private static String TOKEN = "token!Q@W3e4r";
/**
* 生成token
* @param map //传入payload
* @return 返回token
*/
public static String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,7);
builder.withExpiresAt(instance.getTime());
return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}
/**
* 验证token
* @param token
* @return
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
/**
* 获取token中payload
* @param token
* @return
*/
public static DecodedJWT getToken(String token){
return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
}

七、整合springboot

1
2
3
markdown复制代码# 0.搭建springboot+mybatis+jwt环境
- 引入依赖
- 编写配置
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
xml复制代码<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>

<!--引入mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>

<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>

<!--引入druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>

<!--引入mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
properties复制代码server.port=8989
spring.application.name=jwt

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root

mybatis.type-aliases-package=com.pojo
mybatis.mapper-locations=classpath:com/mapper/*.xml

logging.level.com.dao=debug
1
2
markdown复制代码# 1.开发数据库
- 这里采用最简单的表结构验证JWT使用

在这里插入图片描述

1
2
3
4
5
6
7
sql复制代码DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(80) DEFAULT NULL COMMENT '用户名',
`password` varchar(40) DEFAULT NULL COMMENT '用户密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
1
markdown复制代码# 2.开发entity
1
2
3
4
5
6
7
java复制代码@Data
@Accessors(chain=true)
public class User {
private String id;
private String name;
private String password;
}

在这里插入图片描述

1
markdown复制代码# 3.开发DAO接口和mapper.xml
1
2
3
4
java复制代码@Mapper
public interface UserDAO {
User login(User user);
}

在这里插入图片描述

1
2
3
4
5
6
xml复制代码<mapper namespace="com.dao.UserDAO">
<!--这里就写的简单点了毕竟不是重点-->
<select id="login" parameterType="User" resultType="User">
select * from user where name=#{name} and password = #{password}
</select>
</mapper>

在这里插入图片描述

1
markdown复制代码# 4.开发Service 接口以及实现类
1
2
3
java复制代码public interface UserService {
User login(User user);//登录接口
}

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
User userDB = userDAO.login(user);
if(userDB!=null){
return userDB;
}
throw new RuntimeException("登录失败~~");
}
}

在这里插入图片描述

1
markdown复制代码# 5.开发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
java复制代码@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/login")
public Map<String,Object> login(User user) {
Map<String,Object> result = new HashMap<>();
log.info("用户名: [{}]", user.getName());
log.info("密码: [{}]", user.getPassword());
try {
User userDB = userService.login(user);
Map<String, String> map = new HashMap<>();//用来存放payload
map.put("id",userDB.getId());
map.put("username", userDB.getName());
String token = JWTUtils.getToken(map);
result.put("state",true);
result.put("msg","登录成功!!!");
result.put("token",token); //成功返回token信息
} catch (Exception e) {
e.printStackTrace();
result.put("state","false");
result.put("msg",e.getMessage());
}
return result;
}
}

在这里插入图片描述

1
markdown复制代码# 6.数据库添加测试数据启动项目

在这里插入图片描述

在这里插入图片描述

1
markdown复制代码# 7.通过postman模拟登录失败

在这里插入图片描述

1
markdown复制代码# 8.通过postman模拟登录成功

在这里插入图片描述

1
markdown复制代码# 9.编写测试接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码@PostMapping("/test/test")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
try {
JWTUtils.verify(token);
map.put("msg", "验证通过~~~");
map.put("state", true);
} catch (TokenExpiredException e) {
map.put("state", false);
map.put("msg", "Token已经过期!!!");
} catch (SignatureVerificationException e){
map.put("state", false);
map.put("msg", "签名错误!!!");
} catch (AlgorithmMismatchException e){
map.put("state", false);
map.put("msg", "加密算法不匹配!!!");
} catch (Exception e) {
e.printStackTrace();
map.put("state", false);
map.put("msg", "无效token~~");
}
return map;
}

在这里插入图片描述

1
markdown复制代码# 10.通过postman请求接口

在这里插入图片描述
在这里插入图片描述

1
2
3
markdown复制代码# 11.问题?
- 使用上述方式每次都要传递token数据,每个方法都需要验证token代码冗余,不够灵活? 如何优化
- 使用拦截器进行优化
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
java复制代码@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<>();
try {
JWTUtils.verify(token);
return true;
} catch (TokenExpiredException e) {
map.put("state", false);
map.put("msg", "Token已经过期!!!");
} catch (SignatureVerificationException e){
map.put("state", false);
map.put("msg", "签名错误!!!");
} catch (AlgorithmMismatchException e){
map.put("state", false);
map.put("msg", "加密算法不匹配!!!");
} catch (Exception e) {
e.printStackTrace();
map.put("state", false);
map.put("msg", "无效token~~");
}
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
1
2
3
4
5
6
7
8
9
java复制代码@Component
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtTokenInterceptor()).
excludePathPatterns("/user/**")
.addPathPatterns("/**");
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…148149150…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%