-
- 介绍
电商常见类型:
类型 | 全称 | 解释 | 代表 |
B2C | Business To Consumer | 商家对个人 | 天猫、京东、亚马逊 |
B2B | Business To Business | 商家对商家 | 阿里巴巴 |
C2C | Consumer To Consumer | 个人对个人 | 淘宝、闲鱼、拍拍 |
O2O | Online To Offline | 线上&线下 | 国美、苏宁、糯米、口碑、美团、大众点评 |
P2P | Peer To Peer | 点对点网贷 | 人人贷 |
谷粒商城是一个B2C模式的电商平台,销售自营商品给客户。
项目架构
微服务划分
-
- -安装Linux虚拟机
下载&安装 VirtualBox:
安装前要开启 CPU 虚拟化
修改VirtualBox虚拟机文件默认存储位置:
VirtualBox菜单->管理->全局设定->常规->默认虚拟电脑位置:指定一个容量较大的磁盘目录,例如:E:\VirtualBoxDisk\
下载&安装 Vagrant:
Install | Vagrant | HashiCorp Developer ,安装程序会自动把安装路径加入到环境变量PATH
修改Vagrant镜像默认存储HOME目录:
增加环境变量VAGRANT_HOME指定到一个容量较大的磁盘目录下(默认为%User_Home%\.vagrant.d),例如:E:\VirtualBoxDisk\.vagrant.d
初始化一个虚拟centos7系统:
在VAGRANT_HOME下新建目录centos7,进入centos7,打开window cmd窗口,运行命令:
vagrant init centos/7
Vagrant会从官方镜像仓库(Discover Vagrant Boxes - Vagrant Cloud)下载名为“centos/7”的镜像到VAGRANT_HOME,并在当前centos7目录下生成一个Vagrantfile配置文件
启动虚拟centos7系统:
运行命令vagrant up即可启动虚拟机实例,VirtualBox将自动添加启动的实例。
系统root用户和vagrant用户的密码都是vagrant
运行命令vagrant ssh可连接虚拟机实例
配置网络:
以太网适配器 VirtualBox Host-Only Network的属性:
设置固定IPv4地址为192.168.56.1,用来当做虚拟机实例的网关
设置子网掩码为255.255.255.0,其他为空的不管
虚拟机实例的网络连接方式默认为使用网络地址转换(NAT)和端口转发,开发不方便,修改为仅主机网络(Host-Only):
修改Vagrantfile文件配置(取消注释并改ip值):config.vm.network "private_network", ip: "192.168.56.10"
运行命令vagrant reload加载Vagrantfile配置并重启虚拟机实例
互ping验证宿主机与虚拟机实例是否连通:
宿主机:ping 192.168.56.10
虚拟机实例:vagrant ssh进去系统之后:
ip addr命令查看是否新增了ip为192.168.56.10的网卡
ping 宿主机ip
配置账号:
默认只允许运行命令vagrant ssh登录,不方便开发,修改为允许账号密码登录:
vagrant ssh进去系统之后,切换为root用户,
vi /etc/ssh/sshd_config
修改 PasswordAuthentication值no改为yes
重启服务service sshd restart,然后可以ssh远程登录了
更换yum源:
备份原 yum 源
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
使用新 yum 源
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo
生成缓存
yum makecache
更新yum源
yum -y update
-
- -安装 Docker
参见Docker安装章节
-
- -Docker安装Mysql
下载镜像文件
docker pull mysql:5.7
5.7为Image的Tag,获取方式:打开https://hub.docker.com/ 最上边搜索mysql,结果列表第一个点Docker Official Image,详情中点Tags,Filter Tags中搜索5.7,结果列表中查得Tag为5.7的Image
查看下载的镜像:
sudo docker images
创建实例并启动
sudo docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
参数说明
-p 3306:3306:将容器的 3306 端口映射到主机的 3306 端口
--name mysql:设置容器名称
-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机(主机目录:容器内目录),方便在宿主机中查看修改mysql配置
-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机(主机目录:容器内目录),方便在宿主机中查看mysql日志
-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机(主机目录:容器内目录),方便在宿主机中查看修改mysql配置
-e MYSQL_ROOT_PASSWORD=root:初始化 root 用户的密码
-d mysql:5.7:以后台方式运行指定Tag的镜像来生成实例
设置MySQL服务随系统启动
[root@localhost docker]# docker update mysql --restart=always
验证mysql安装
1)查看运行中的容器验证:
docker ps
2)主机或宿主机mysql客户端远程连接验证
服务器地址192.168.56.10,端口3306,用户root,密码root
3)通过容器的 mysql 命令行工具连接验证
docker exec -it mysql mysql -uroot -proot
如果主机或宿主机mysql客户端远程连接不上,在命令行下设置 root 远程访问:
mysql> grant all privileges on *.* to 'root'@'%' identified by 'root' with grant option;
mysql> flush privileges;
3)进入实例内部验证
[root@localhost docker]# sudo docker exec -it mysql /bin/bash
root@82ec82ff53b8:/#
docker exec参数说明:
-it:以交互模式
mysql:容器名称(也可写容器ID)
/bin/bash:进入bash控制台
如果进入后中文乱码,退出增加参数重新进入:sudo docker exec -it mysql env LANG=C.UTF-8 /bin/bash
查看mysql目录
root@82ec82ff53b8:/# whereis mysql
退出容器
root@82ec82ff53b8:/# exit
exit
[root@localhost docker]#
在主机中配置MySQL
[root@localhost docker]# vi /mydata/mysql/conf/my.cnf
[client] default-character-set=utf8 [mysql] default-character-set=utf8 [mysqld] init_connect='SET collation_connection = utf8_unicode_ci' init_connect='SET NAMES utf8' character-set-server=utf8 collation-server=utf8_unicode_ci skip-character-set-client-handshake skip-name-resolve |
参数说明:
skip-name-resolve:跳过域名解析(解决 MySQL 连接慢的问题)
重启MySQL
[root@localhost docker]# docker restart mysql
-
- -Docker安装Redis
下载镜像文件
docker pull redis
不指定Tag则下载最新镜像
预先创建文件防止下一步创建实例指定挂载路径时Docker把文件当目录创建
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
创建实例并启动
docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf
参数说明:
-d redis redis-server /etc/redis/redis.conf:以后台方式运行镜像来生成实例,并且指定redis服务启动时使用的配置文件路径
配置redis持久化并重启redis
vi /mydata/redis/conf/redis.conf
appendonly yes |
docker restart redis
redis 官方配置文件样例:
https://raw.githubusercontent.com/antirez/redis/4.0/redis.conf
设置redis服务随系统启动
[root@localhost docker]# docker update redis --restart=always
连接redis
1)使用 redis 镜像执行 redis-cli 命令连接
docker exec -it redis redis-cli
- 使用可视化工具Redis Desktop Manage连接
服务器地址192.168.56.10,端口6379
-
- -开发环境
Maven配置
配置阿里云镜像
<mirrors> <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors> |
配置使用 JDK1.8编译项目
<profiles> <profile> <id>jdk-1.8</id> <activation> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> </properties> </profile> </profiles> |
配置idea的Maven配置
idea欢迎界面-》Configure-》Settings-》Build, Execution, Deployment-》Build Tools-》Maven
-》Maven home directory:选择Maven的主目录,如:E:/App/apache-maven-3.3.9
-》User settings file:选择上面配置的文件,如:C:\Users\TWX\.m2\settings.xml
注:
- 上述是对idea的全局配置,如果只想对当前项目配置,则在打开项目后,点File-》Settings进行配置。
- 如果idea已打开项目,但要进行全局配置,点File-》Close Project回到欢迎界面,再进行上述操作。
- 对idea的全局配置的修改只对将要新建的项目生效。
IDE配置
idea安装插件
安装 Lombok、MyBatisX插件(MyBatisX插件可帮助我们在mapper方法和xml文件之间跳转)
vscode安装插件
Vetur —— Vue语法高亮、智能感知、Emmet 等,包含格式化功能, Alt+Shift+F (格式化全文),Ctrl+K Ctrl+F(格式化选中代码,两个 Ctrl
需要同时按着)
Volar —— 比Vetur更好的插件
Vue 2 Snippets —— Vue2语法提示
vue-helper —— Vue点击变量跳转到定义
EsLint —— 语法纠错
Auto Close Tag —— 自动闭合 HTML/XML 标签
Auto Rename Tag —— 自动完成另一侧标签的同步修改
JavaScript(ES6) code snippets —— ES6语法智能提示以及快速输入,除js外还支持.ts,.jsx,.tsx,.html,.vue,省去了配置其支持各种包含 js 代码文件的时间
HTML CSS Support —— 让 html 标签上写 class 智能提示当前项目所支持的样式
HTML Snippets —— html 快速自动补全
Open in browser —— 浏览器快速打开
Live Server —— 以内嵌服务器方式打开
Chinese (Simplified) Language Pack for Visual Studio Code —— 中文语言包
vscode代码格式化插件设置
设置元素属性不自动换行
vetur扩展设置-》Vetur › Format: Default Formatter Options-》在settings.json中编辑:
vetur.format.defaultFormatterOptions原内容注释掉,改为以下新内容:
"js-beautify-html": {
"wrap_line_length": 900, // 数值越大,一行放的属性越多
"wrap_attributes": "auto",
"end_with_newline": false
},
"prettyhtml": {
"printWidth": 100,
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
git配置
Git+码云教程:https://gitee.com/help/articles/4104
公钥配置说明:生成/添加SSH公钥 | Gitee 产品文档
配置用户名和邮箱
进入 git bash:
git config --global user.name "用户名"
git config --global user.email "注册码云账号时用的邮箱"
配置SSH免密登录
进入 git bash:
# 生成SSH密钥,输入命令后按提示三次回车,“%USER_HOME%/.ssh”目录下会生成公钥和私钥
ssh-keygen -t rsa -C "注册码云账号时用的邮箱"
查看并复制公钥(注意不要复制末尾的空格或换行符):
cat ~/.ssh/id_rsa.pub
登录进入gitee,找到 账号设置-》安全设置-》SSH公钥,将.pub文件的内容添加进去。
使用 ssh -T git@gitee.com测试是否成功即可。
从码云创建父工程
新建仓库,填入以下信息后点创建:
仓库名称:gulimall
路径(填入名称后会自动跟填):gulimall
仓库介绍:谷粒商城
开源/私有/企业内部开源:自选一个
初始化仓库:打勾
选择语言:Java
添加.gitignore:Maven
添加开源许可证:自选一个,例如Apache-2.0
设置模板:打勾
Readme文件:打勾
选择分支类型:打勾,自选一个,例如生产/开发模型
克隆父工程到本地
- 复制项目地址
- idea菜单-》File-》New-》Project from Version Control-》Git-》填入以下内容后点Clone:
URL:填入复制的项目地址
Directory:选择本地目录,例如:E:\Repositories\gulimall
创建各个微服务模块(Spring Boot项目)
创建以下五个微服务模块(黄色标为差异化内容)
- File菜单或项目右键-》New-》Module-》Spring Initializr
- 保持默认的JDK配置(1.8)和Initializr(Default)-》Next-》
- 元数据配置界面,填入以下信息后点Next:
Group:com.atguigu
Artifact:gulimall-product/order/ware/coupon/member
Type:Maven
Language:Java
Packaging:Jar
Java Version:8
Description:谷粒商城-商品服务/订单服务/仓储服务/优惠券服务/会员服务
Package:com.atguigu.gulimall.product/order/ware/coupon/member
(Version保持默认,Name会自动跟填Artifact)
- 依赖选择界面,Sping Boot版本选2.x的,依赖选择Spring Web、OpenFeign后点Next-》Finish
- 将所有模块pom中:
- 检查java.version是否为1.8,若为17,说明依赖选择界面Sping Boot版本选错了,要删除该模块重建
- spring-boot-starter-parent的<version>改为2.1.8.RELEASE
- spring-cloud.version改为Greenwich.SR3
6)修正Gulimall***ApplicationTests代码
配置父工程pom
复制一个模块的pom文件到项目下(E:\Repositories\gulimall\pom.xml),删除<project>下的所有子元素,添加以下子元素:
<modelVersion>4.0.0</modelVersion> |
打开Maven Projects窗口,点击+号,添加上述文件作为所有模块的父工程
创建一个common模块(maven项目)
- File-》New-》Module-》Maven-》Next-》ArtifactId填gulimall-common-》Module name修改为gulimall-common-》Finish
- 在common模块的pom中添加描述:
<description>谷粒商城-公共服务</description> |
2)在父工程的pom中添加common模块(如果没有自动加):
<module>gulimall-common</module> |
4)给其他模块的pom添加common依赖:
<dependency> |
配置微服务模块端口号(application.yml)
coupon模块 | member模块 | order模块 | product模块 | ware模块 |
server: | server: | server: | server: | server: |
配置ignore
在父工程下的.gitignore文件中追加以下忽略项:
**/mvnw **/mvnw.cmd **/.mvn **/target/ .idea **/.gitignore |
提交代码并推送gitee
初始化数据库数据
gulimall_oms
gulimall_pms
gulimall_sms
gulimall_ums
gulimall_wms
使用人人开源项目快速搭建后台管理系统脚手架
人人开源官网:人人开源
人人开源gitee:人人开源
前端搭建
- 使用课件资源包里的renren-fast-vue项目,不要下载人人开源gitee上的(代码有更新导致无法npm install)
- 下载安装node-v10.16.3-x64.msi(尽量保证版本一致,否则可能无法npm install),命令行node -v检查安装结果
- 配置 npm 使用淘宝镜像:npm config set registry http://registry.npm.taobao.org/
- 将renren-fast-vue文件夹移动到E:\Repositories\下,右击-》通过Code打开
- 打开vscode终端,输入命令以下载依赖(根据package.json下载到node_modules中):npm install
如果 npm install 安装依赖出现 chromedriver 之类问题,在终端运行如下命令后再运行npm install:
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
如果报错,根据提示链接手动下载chromedriver_win32.zip到E:\Repositories\renren-fast-vue\,然后改用命令:
npm install chromedriver --chromedriver_filepath=chromedriver_win32.zip
9)启动项目:终端命令npm run dev
10)启动成功后将弹出登录页面,账密:admin/admin
11)Chrome安装Vue的开发者工具:Chrome应用商店直接翻墙安装Vue.js devtools,然后重新vscode代码编辑页面右键-》Open with Live Server,开发者工具上会多出一个“Vue”标签。注意如果要使用Vue.js devtools,引入的vue脚本应为开发版。
12)关闭ESlint代码检查:项目目录下build->webpack.base.conf.js文件->注释掉createLintingRule方法体内容。(重启项目后生效)
后台搭建
1)使用课件资源包里的renren-fast和renren-generator项目,不要下载人人开源gitee上的(代码有更新,麻烦)
2)在父工程pom文件中添加模块:
<module>renren-fast</module> <module>renren-generator</module> |
- idea菜单-》File-》Project Structure-》Project Settings-》Modules-》“+”号-》Import Module-》依次导入新加入的两个模块(若无)
- 给renren-fast模块的pom添加common依赖
- 创建数据库gulimall_admin,将renren-fast\db\mysql.sql导入数据库
4)修改配置文件renren-fast/src/main/resources/application-dev.yml的mysql连接信息:url、username、password,连接库为gulimall_admin
逆向工程准备工作
1)在common模块的pom添加其他模块所需公共依赖:
<dependencies> <!--mybatis-plus--> |
2)在common模块添加其他模块所需公共类:
-
- 创建包com.atguigu.common.utils,并将renren-fast模块的以下类复制到该包下:
- io.renren.common.utils.Constant
- io.renren.common.utils.PageUtils
- io.renren.common.utils.Query
- io.renren.common.utils.R
- io.renren.common.exception.RRException
- 创建包com.atguigu.common.xss,并将renren-fast模块的以下类复制到该包下:
- io.renren.common.xss.HTMLFilter
- io.renren.common.xss.SQLFilter
- 修正com.atguigu.common.xss.SQLFilter类中RRException的import语句
- 修正com.atguigu.common.utils.Query类中SQLFilter的import语句
- 创建包com.atguigu.common.utils,并将renren-fast模块的以下类复制到该包下:
- 修改renren-generator模块的template/Controller.java.vm文件,搜索RequiresPermissions所在行注释掉(6处),我们使用Spring Security不使用shiro
逆向工程(黄色标为差异化内容):
1)修改配置文件renren-generator/src/main/resources/application.yml的mysql连接信息:url、username、password,连接库为需要逆向生成代码的库:gulimall_pms/gulimall_oms/gulimall_wms/gulimall_sms/gulimall_ums
3)修改配置文件renren-generator/src/main/resources/generator.properties的以下配置:
mainPath=com.atguigu
package=com.atguigu.gulimall
moduleName=product/order/ware/coupon/member
(author为git配置的user.name)
(email为git配置的user.email)
tablePrefix=pms_/oms_/wms_/sms_/ums_
- 启动renren-generator的io.renren.RenrenApplication#main
- 打开http://localhost/ 即为逆向工程网页控制台,选择全部表进行逆向生成代码
- 将生成的代码解压,取其中main目录复制到E:\Repositories\gulimall\gulimall-product\src下
- 删除不需要的vue代码目录:E:\Repositories\gulimall\gulimall-product/order/ware/coupon/member\src\main\resources\src
- MyBatis-Plus
以product模块为例
导入依赖
common模块pom添加数据库驱动和MyBatis-Plus对应的starter(逆向工程若已添加,则此步跳过);
<!--mysql--> |
注:mysql8驱动兼容mysql5数据库
配置数据源
product模块的配置文件application.yml中添加如下配置:
spring: |
配置MyBatis-Plus
扫描Mapper接口:product模块的启动类上添加注解:
import org.mybatis.spring.annotation.MapperScan; @MapperScan("com.atguigu.gulimall.product.dao") |
注:如果不配置@MapperScan,就需要给每一个Mapper接口添加@Mapper注解,才能被识别。
指定映射文件位置和entity主键自增:
mybatis-plus: |
注:classpath和classpath*的区别:classpath只扫描当前模块类路径,classpath*还扫描引用jar包的类路径
数据层接口使用BaseMapper简化开发
@Mapper public interface BookDao extends BaseMapper<Book> { } |
分页功能
package com.itheima.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } } |
@Test void testGetPage(){ IPage page = new Page(2,5);//第几页、每页几条 IPage iPage = bookDao.selectPage(page, null); System.out.println(iPage.getPages()); System.out.println(iPage.getCurrent()); System.out.println(iPage.getRecords()); System.out.println(iPage.getSize()); System.out.println(iPage.getTotal()); } |
条件查询功能
@Test void testGetBy1(){ QueryWrapper<Book> wq = new QueryWrapper<>(); wq.like("name","Spring"); bookDao.selectList(wq); } @Test void testGetBy2(){ String name = "1"; LambdaQueryWrapper<Book> wq = new LambdaQueryWrapper<Book>(); // if(name != null) wq.like(Book::getName,name); wq.like(name != null,Book::getName,name); bookDao.selectList(wq); } |
业务层简化开发(不推荐)
public interface IBookService extends IService<Book> { //添加非通用操作API接口 } |
@Service public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService { @Autowired private BookDao bookDao; //添加非通用操作API } |
-
- SpringCloud Alibaba
简介
Spring Cloud Alibaba致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
为什么使用
SpringCloud 的几大痛点
SpringCloud 部分组件停止维护和更新,给开发带来不便;
SpringCloud 部分环境搭建复杂,没有完善的可视化界面,我们需要大量的二次开发和定制
SpringCloud 配置复杂,难以上手,部分配置差别难以区分和合理应用
SpringCloud Alibaba 的优势
阿里使用过的组件经历了考验,性能强悍,设计合理,现在开源出来大家用
成套的产品搭配完善的可视化界面给开发运维带来极大的便利
搭建简单,学习曲线低。
本项目技术搭配方案
SpringCloud Alibaba Nacos : 注册中心(服务发现/ 注册)
SpringCloud Alibaba Nacos : 配置中心(动态配置管理)
SpringCloud Ribbon : 负载均衡
SpringCloud Feign : 声明式 HTTP 客户端(调用远程服务)
SpringCloud Alibaba Sentinel : 服务容错(限流、降级、熔断)
SpringCloud Gateway : API 网关(webflux 编程模式)
SpringCloud Sleuth : 调用链监控
SpringCloud Alibaba Seata原 : 原 Fescar
版本选择
由于 Spring Boot 1 和 Spring Boot 2 在 Actuator 模块的接口和注解有很大的变更,且spring-cloud-commons 从 1.x.x 版本升级到 2.0.0 版本也有较大的变更,因此我们采取跟SpringBoot 版本号一致的版本:
l 1.5.x 版本适用于 Spring Boot 1.5.x
l 2.0.x 版本适用于 Spring Boot 2.0.x
l 2.1.x 版本适用于 Spring Boot 2.1.x
引入SpringCloud Alibaba
在common模块pom中引入如下。进行统一管理:
<dependencyManagement> |
-
- Nacos Discovery
简介
Nacos /nɑ:kəʊs/ 是Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos 支持几乎所有主流类型的服务的发现、配置和管理:
Kubernetes Service
gRPC & Dubbo RPC Service
Spring Cloud RESTful Service
Nacos 使用 Java 编写。需要依赖 Java 环境。
Nacos 文档地址: Nacos 快速开始
使用
下载 nacos-server
Releases · alibaba/nacos · GitHub
启动 nacos-server
l 双击 bin 中的 startup.cmd 文件
l 访问提示的Console地址或 http://localhost:8848/nacos/
l 使用默认的 nacos/nacos 进行登录
引入依赖
在common模块pom中引入Nacos Discovery Starter:
<!--nacos-discovery--> |
编写通信信息
在coupon模块/src/main/resources/application.yml配置文件中配置应用名、端口号和注册中心地址:
spring.application.name=gulimall-coupon
server.port=8000
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
注意:server.port前面已配
开启功能
在coupon模块启动类中使用@EnableDiscoveryClient开启服务注册发现功能
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
启动应用并验证
启动coupon应用后,在nacos控制台点服务管理-》服务列表,查看是否有gulimall-coupon服务
-
- Nacos Config
简介
Nacos配置项组织层次
配置集:一个配置集通常对应一个配置文件,一个配置文件通常是一组相关配置项的集合。
配置分组:对配置集按功能或环境分组,默认配置分组为DEFAULT_GROUP
命名空间:Nacos命名空间用来做环境隔离或租户隔离或服务隔离,默认命名空间为public。不同命名空间下可以存在相同的Group或Data ID
每个微服务创建自己的 namespace 进行隔离,group 来区分 dev,beta,prod 等环境
本项目配置项组织方案:
不同环境使用不同的配置中心进行隔离(企业中不同环境是完全物理隔离的)
同一环境下每个微服务对应一个命名空间进行隔离
暂时不使用配置分组功能(配置分组一般用于临时切换同名配置集,比如购物节要临时用几天新配置,过后恢复旧配置)。
原理
自动注入:
NacosConfigStarter实现了 org.springframework.cloud.bootstrap.config.PropertySourceLocator接口,并将优先级设置成了最高。
在 Spring Cloud 应用启动阶段,会主动从 Nacos Server 端获取对应的数据,并将获取到的数据转换成 PropertySource 注入到 Environment 的 PropertySources 属性中,所以使用@Value 注解也能直接获取 Nacos Server 端配置的内容。
动态刷新:
Nacos Config Starter 默认为所有获取数据成功的 Nacos 的配置项添加了监听功能,在监听到服务端配置MD5发生变化时会实时触发org.springframework.cloud.context.refresh.ContextRefresher 的 refresh 方法。
如果需要对 Bean 进行动态刷新,给类添加@RefreshScope 或 @ConfigurationProperties注解。
使用
在Nacos控制台添加配置
新建命名空间:
Nacos控制台-》命名空间-》新建命名空间coupon,将自动生成命名空间ID
添加配置集:
Nacos控制台-》配置管理-》配置列表-》选择命名空间coupon:
添加一个主配置集配置SpringBoot:点+号新建配置集-》填写以下信息和配置后点发布:
Data ID:application.yml
(Group使用默认)
配置格式:选yaml
配置内容:原coupon模块SpringBoot配置文件application.yml中的SpringBoot相关配置项
添加一个扩展配置集配置mysql数据源:点+号新建配置集-》填写以下信息和配置后点发布:
Data ID:application-datasource.yml
(Group使用默认)
配置格式:选yaml
配置内容:原coupon模块SpringBoot配置文件application.yml中的mysq数据源相关配置项
添加一个扩展配置集配置mybatis:点+号新建配置集-》填写以下信息和配置后点发布:
Data ID:application-mybatis.yml
(Group使用默认)
配置格式:选yaml
配置内容:原coupon模块SpringBoot配置文件application.yml中的mybatis相关配置项
注意:nacos配置中心的配置会覆盖本地配置文件的配置。
引入依赖
在common模块pom中引入Nacos Config Starter:
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
编写配置中心元数据
coupon模块/src/main/resources/bootstrap.properties:
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=9fa45407-fc82-45cc-b07e-8a12443c2859
#spring.cloud.nacos.config.group=
spring.cloud.nacos.config.prefix=application
spring.cloud.nacos.config.file-extension=yml
spring.cloud.nacos.config.ext-config[0].data-id=application-datasource.yml
spring.cloud.nacos.config.ext-config[0].refresh=true
#spring.cloud.nacos.config.ext-config[0].group=
spring.cloud.nacos.config.ext-config[1].data-id=application-mybatis.yml
spring.cloud.nacos.config.ext-config[1].refresh=true
#spring.cloud.nacos.config.ext-config[1].group=
#spring.application.name=gulimall-coupon
#spring.profiles.active=dev
常用参数说明:
spring.cloud.nacos.config.server-addr:配置中心地址
spring.cloud.nacos.config.namespace:配置集所属命名空间ID。不配置则默认为public
spring.cloud.nacos.config.group:配置集所属配置分组。不配置则默认为DEFAULT_GROUP
spring.cloud.nacos.config.prefix:配置集Data Id的前缀。不配置则默认为NVL(${spring.application.name}, "null")
spring.cloud.nacos.config.file-extension:配置集Data Id的扩展名。不配置则默认为properties
spring.cloud.nacos.config.ext-config[<index>].*:指定扩展配置集,相当于SpringBoot主配置文件中include其他配置文件 spring.application.name:应用名。如果该参数和spring.cloud.nacos.config.prefix参数都未配置,则配置集Data Id的前缀为“null”
spring.profiles.active:优先从Data Id前缀后接“-${spring.profiles.active}”的配置集中查找,如果找不到再从不带的中找
在应用中获取配置
- 在Spring组件的类上添加注解:@RefreshScope
- 在Spring组件使用@Value或Environment或@ConfigurationProperties获取配置项
- 如果配置中心找不到指定配置项,Spring会再从本地配置文件中找
与携程配置中心Apollo的对比
Nacos | Apollo | |
权限控制 | 角色权限粗粒度 不同环境使用同一个配置中心,但使用命名空间隔离。用户访问需要登录。 | 角色权限细粒度 不同环境使用不同的配置中心。用户访问不需登录 |
配置组织层级 | namespace-》group-》dataId | env-》namespace/appId-》cluster |
框架设计 | config service、admin service、portal | |
分布式高可用最小集群数量 | Nacos*3+MySQL=4 | Config*2+Admin*3+Portal*2+MySQL=8 |
-
- OpenFeign
简介
Feign是一个声明式的 HTTP 客户端,它的目的就是让远程调用更加简单。Feign 提供了 HTTP请求的模板, 通过编写简单的接口和插入注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。
Feign的英文含义是伪装,Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。
Feign 整合了 Ribbon (负载均衡)和 Hystrix( 服务熔断),可以让我们不再需要显式地使用这两个组件。
SpringCloudFeign 在 NetflixFeign 的基础上扩展了对 SpringMVC 注解的支持,在其实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。简化了SpringCloudRibbon 自行封装服务调用客户端的开发量。
原理
- 基于面向接口的动态代理方式生成实现类
- 根据接口类的注解声明规则,解析出底层MethodHandler
- 基于RequestBean动态生成Request
- Encoder将Bean包装成请求
- 拦截器负责对请求和返回进行装饰处理
- 日志记录
- 基于重试器发送Http请求,可基于不同的Http框架处理
使用
本示例使用OpenFeign从member模块远程调用coupon模块,使用前先把gulimall-member服务也注册到nacos上。
引入依赖
在member模块pom中引入Openfeign Starter:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
注:项目初始化时已经引入,此步跳过
开启功能
在member模块启动类中使用@EnableFeignClients开启Feign功能并指定开启范围:
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
创建远程调用接口
- 在member模块创建远程调用接口
- 使用@FeignClient指定要调用的服务(默认属性value为服务端在Nacos中注册的服务名,可选属性path可抽取@RequestMapping值的公共部分)
- 复制要调用的服务端方法(去除方法体)
- 使用@RequestMapping指定请求路径
package com.atguigu.gulimall.member.feign; |
测试验证
在member模块的MemberController 中测试验证:
@RestController @Autowired |
浏览器请求:http://localhost:8000/member/member/testCouponFeignServiceList
简介
API 网关是介于客户端和服务器之间的中间层,汇聚了各个服务的对外API,所有的外部请求都会先经过API网关这一层。
API网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制、监控日志、安全策略等。
SpringCloud Gateway作为 SpringCloud 官方推出的第二代网关框架,取代了 Zuul 网关。
SpringCloud Gateway由Netty编写。
官方文档地址:Spring Cloud Gateway
网关组成
网关 = 路由转发 + 过滤器(编写额外功能)
路由转发
其实就是一种转发规则,把满足什么样的规则的地址转发到什么服务上。
接收外界请求,通过网关的路由转发,转发到后端的服务上。
如果只有这个功能看起来和Nginx反向代理服务器很像,外界访问Nginx,有nginx做负载均衡,后把请求转发到对应的服务器上。
过滤器
网关非常重要的功能就是过滤器。
过滤器中默认提供了25中内置功能,还支持额外的自定义功能。
对于我们来说比较常用的功能有网关的容错、限流以及请求即相应的额外处理。
Spring Cloud Gateway 特点:
l 基于 Spring5,支持响应式编程和 SpringBoot2.0
l 支持使用任何请求属性进行路由匹配
l 特定于路由的断言和过滤器
l 集成 Hystrix 进行断路保护
l 集成服务发现功能
l 易于编写 Predicates 和 Filters
l 支持请求速率限制
l 支持路径重写
为什么使用 API 网关?
API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的 问题:
l 客户端会多次请求不同的微服务,增加了客户端的复杂性。
l 存在跨域请求,在一定场景下处理相对复杂。
l 认证复杂,每个服务都需要独立认证。
l 难以重构,随着项目的迭代,可能需要重新划分微服务(例如微服务合并或拆分)。如果客户端直接与微服务通信,那么重构将会很难实施。
l 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。
核心概念
- 路由(Route):网关最基础的部分,一个路由包含ID、URI、[Predicate集合]、[Filter集合]。如果断言路由为真,则说明请求的 URL 和配置匹配
- 断言(Predicate):断言/谓词就是一些附加条件和内容。Java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是 Spring5.0 框架中的 ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自于 http request 中的任何信息,比如请求头和参数等。
- 过滤器(Filter):一个标准的 Spring webFilter。Spring cloud gateway 中的 filter 分为两种类型的Filter,分别是 Gateway Filter 和 Global Filter。过滤器负责在代理服务"之前”或“之后”去做一些事情,比如对请求和响应进行修改处理
原理
客户端发送请求给网关,网关 HandlerMapping 判断请求是否满足断言,一旦满足其中一个就发给网关的 WebHandler。这个 WebHandler 将请求交给一个过滤器链,请求到达目标服务之前,会执行所有过滤器的 pre 方法。请求经目标服务处理之后再依次执行所有过滤器的 post 方法。
使用
创建网关模块
项目类型:Spring Initializr
元数据:
Group:com.atguigu
Artifact:gulimall-gateway
Type:Maven
Language:Java
Packaging:Jar
Java Version:8
Description:谷粒商城-API网关
Package:com.atguigu.gulimall.gateway
(Version默认,Name跟填Artifact)
Sping Boot版本:选2.x
依赖选择:Gateway
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
检查、修改pom:
a.检查java.version是否为1.8,若为17,说明依赖选择界面Sping Boot版本选错了,要删除该模块重建
b.spring-boot-starter-parent的<version>改为2.1.8.RELEASE
c.spring-cloud.version改为Greenwich.SR3
修正GulimallGatewayApplicationTests代码
使用注册中心和配置中心
- gulimall-gateway/pom.xml中引入:gulimall-common(需要其中的acos-discovery和acos-config)
- 启动类GulimallGatewayApplication上添加注解:@EnableDiscoveryClient
- Nacos控制台新建命名空间gateway,添加配置集application.properties:
spring.application.name=gulimall-gateway
server.port=88
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
- 添加配置中心元数据gulimall-gateway\src\main\resources\bootstrap.properties:
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=f840cbc1-93b8-4b61-85b1-aea8dab52d8e
#spring.cloud.nacos.config.group=
spring.cloud.nacos.config.prefix=application
#spring.cloud.nacos.config.file-extension=
- 引入的gulimall-common依赖引入了mybatis-plus-boot-starter依赖,但gateway模块不需要,排除之:
- 方法一:启动类注解添加exclude属性来禁止SpringBoot自动注入数据源配置:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
-
- 方法二:在引入gulimall-common的<dependency>标签下增加<exclusions>排除项:
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
动态路由(不建议开启)
动态路由可以与注册中心结合,开启动态路由后,会为注册到注册中心的每个微服务都生成一个默认路由:id为大写的微服务名称
动态路由不建议开启,因为开启了会暴露后端服务
如要开启,在application.properties中添加以下配置:
# 是否结合注册中心,通过微服务名称转发到具体的服务实例【默认false,建议false】
spring.cloud.gateway.discovery.locator.enabled=true
# 小写服务名(此参数设为true是为应对eureka自动大写serviceId引发的问题)
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
开启动态路由和小写服务名后,只要请求地址符合规则:http://网关IP:网关端口/小写的微服务名称/微服务API
Gateway就会自动转发到:http://微服务名称/微服务API
例如:有微服务,命名是user
请求地址是:http://localhost:9999/user/getUserInfo
自动转发到:http://user/getUserInfo
使用断言
断言工厂(RoutePredicateFactory):
类型 | 谓词工厂 | 说明 |
datetime | AfterRoutePredicateFactory | 请求时间满足在配置时间之后 |
datetime | BeforeRoutePredicateFactory | 请求时间满足在配置时间之前 |
datetime | BetweenRoutePredicateFactory | 请求时间满足在配置时间之间 |
Cookie | CookieRoutePredicateFactory | 请求指定Cookie正则匹配指定值 |
Header | HeaderRoutePredicateFactory | 请求指定Headers正则匹配指定值 |
Header | CloudFoundryRouteServiceRoutePredicateFactory | 请求指定Headers是否包含指定名称 |
Method | MethodRoutePredicateFactory | 请求Method匹配配置的Methods |
Path | PathRoutePredicateFactory | 请求路径正则匹配指定值 |
Queryparam | QueryRoutePredicateFacotry | 请求查询参数正则匹配指定值 |
Remoteaddr | 请求远程地址匹配配置指定值 | |
Host | HostRoutePredicateFactory | 请求Host匹配指定值 |
断言示例
application.properties中添加路由规则:
spring.cloud.gateway.routes[0].id=baidu_route
spring.cloud.gateway.routes[0].uri=https://www.baidu.com
spring.cloud.gateway.routes[0].predicates[0]=Query=url, baidu
spring.cloud.gateway.routes[1].id=qq_route
spring.cloud.gateway.routes[1].uri=https://www.qq.com
spring.cloud.gateway.routes[1].predicates[0]=Query=url, qq
注:
1)断言工厂配置可写成一行:<name>=<args>,也可写成两行:name=<name>和args=<args>,例如:
一行写法:...predicates[0]=Path=/api/**
两行写法:...predicates[0].name=Path和...predicates[0].args=/api/**
2)配置断言工厂名称时只需取类名除去RoutePredicateFactory后缀即可
启动模块,浏览器发送请求测试:
http://localhost:88/?url=baidu
http://localhost:88/?url=qq
使用过滤器
过滤器示例
application.properties中添加路由规则:
spring.cloud.gateway.routes[0].id=test
spring.cloud.gateway.routes[0].uri=lb://test-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/test/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
注:
1)过滤器工厂配置可写成一行:<name>=<args>,也可写成两行:name=<name>和args=<args>,例如:
一行写法:...filters[0]=StripPrefix=1
两行写法:...filters[0].name=StripPrefix和...filters[0].args=1
2)配置置过滤器工厂名称时只需取类名除去GatewayFilterFactory后缀即可
测试请求:http://localhost:9999/api/test/getArgs
满足断言:Path=/api/test/**,
路由到lb://test-service,转换成:http://test-service/api/test/getArgs
过滤器是去掉1节前缀,即删除/api,最终地址:http://test-service/test/getArgs
配置网关路由
教学视频中的方法:
修改renren-fast-vue模块static\config\index.js文件,将baseUrl改为网关地址:
// window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
将renren-fast后端注册到注册中心,让网关发现:
代码略
在网关application.properties中配置路由转发:
spring.cloud.gateway.routes[0].id=product_route
spring.cloud.gateway.routes[0].uri=lb://gulimall-product
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/product/**
spring.cloud.gateway.routes[0].filters[0]=RewritePath=/api/(?<segment>/?.*), /${segment}
spring.cloud.gateway.routes[1].id=admin_route
spring.cloud.gateway.routes[1].uri=lb://renren-fast
spring.cloud.gateway.routes[1].predicates[0]=Path=/api/**
spring.cloud.gateway.routes[1].filters[0]=RewritePath=/api/(?<segment>/?.*), /renren-fast/${segment}
注:
- Path断言更精确的路由配置要优先匹配(在properties中数组下标要更小)
- uri=lb://xxx这种写法表示使用负载均衡(load balance)去注册中心找xxx服务
- ...predicates[0]=Path=/api/**也可以写成两行:...predicates[0].name=Path和...predicates[0].args=/api/**
- 在yml配置中$要写成$\吗?
路径匹配断言参考:Spring Cloud Gateway
路径重写过滤参考:Spring Cloud Gateway
测试:renren-fast-vue登录界面请求获取验证码图像:
http://localhost:88/api/captcha.jpg?uuid=4bc560d8-9ee7-434f-8af4-126b86c07018
网关路径匹配断言到:
http://localhost:8080/api/captcha.jpg?uuid=4bc560d8-9ee7-434f-8af4-126b86c07018
网关路径重写过滤到:
http://localhost:8080/renren-fast/captcha.jpg?uuid=4bc560d8-9ee7-434f-8af4-126b86c07018
本人方法:
不修改renren-fast-vue模块static\config\index.js文件中baseUrl配置。
将renren-fast后端注册到注册中心,让网关发现:代码略。
在网关application.properties中配置路由转发:
spring.cloud.gateway.routes[0].id=admin_route
spring.cloud.gateway.routes[0].uri=lb://renren-fast
spring.cloud.gateway.routes[0].predicates[0]=Path=/renren-fast/**
路径匹配断言参考:https://cloud.spring.io/spring-cloud-gateway/reference/html/#the-path-route-predicate-factory
测试:renren-fast-vue登录界面请求获取验证码图像:
http://localhost:88/renren-fast/captcha.jpg?uuid=4bc560d8-9ee7-434f-8af4-126b86c07018
网关路径匹配断言到:
http://localhost:8080/renren-fast/captcha.jpg?uuid=4bc560d8-9ee7-434f-8af4-126b86c07018
解决跨域问题:
问题:
renren-fast管理平台登录报错:
Access to XMLHttpRequest at 'http://localhost:88/renren-fast/sys/login' from origin 'http://localhost:8001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
原因:
CORS:全称Cross-origin resource sharing(跨域资源共享),它允许浏览器向跨域服务器发送Ajax请求
CORS policy:同源策略:指浏览器不能执行其他网站的脚本,只要协议、域名、端口有一个不同(域名对应IP也算不同)都会产生跨域。
只要Url开头到端口号为止的内容不一样,就算跨域。
非简单请求跨域时需要先发送预检请求,只有响应允许跨域,才不会拦截真实请求。
简单请求判定参考:跨源资源共享(CORS) - HTTP | MDN
解决方法一:使用nginx部署为同一域
静态请求:http://nginx/xxx直接发送到nginx中的renren-fast-vue模块
动态请求:http://nginx/api/xxx发送到gateway转发到各个微服务
解决方法二:在网关配置允许请求跨域
注释掉renren-fast模块的io.renren.config.CorsConfig#addCorsMappings方法
在网关模块添加com.atguigu.gulimall.gateway.config.GulimallCorsConfiguration:
package com.atguigu.gulimall.gateway.config; |
启动renren-fast-vue前端和renren-fast后端-》使用账密admin/admin登录管理平台-》系统管理-》菜单管理:
新增目录“商品系统”,并在该目录下新增菜单:
“分类维护”,菜单路由为product/category
“品牌管理”,菜单路由为product/brand
新增目录和菜单后会在gulimall_admin库sys_menu表下生成记录
前端新建vue组件src\views\modules\product\category.vue,使用element-ui的tree组件——基础用法:https://element.eleme.cn/#/zh-CN/component/tree
删除示例数据:将模型中data值置为[]
请求后端分类树数据:
created()中添加调用:this.getTree();
methods中添加方法:getTree(),方法逻辑参考src\views\modules\sys\role.vue的getDataList ()
逻辑删除
说明
mybatis-plus官方说明:逻辑删除 | MyBatis-Plus
只对自动注入的 sql 起效:
插入: 不作限制
查找: 追加 where 条件过滤掉已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
更新: 追加 where 条件防止更新到已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
删除: 转变为 更新
例如:
删除: update user set deleted=1 where id = 1 and deleted=0
查找: select id,name,deleted from user where deleted=0
使用
(可选)配置全局逻辑删除规则:
# 逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
实体类字段上加上@TableLogic注解(黄色标):
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1", delval = "0")
private Integer showStatus;
-
- Aliyun OSS
简介
对象存储两种方式比较
自建服务:FastDFS、vsftpd。搭建复杂,维护成本高,前期费用高。
云存储(Object Storage Service,OSS):阿里云对象存储、七牛云存储、腾讯云存储。即开即用,无需维护,按量收费。
阿里云对象存储资源术语
Bucket:存储空间,即存储对象的容器,所有的对象都必须隶属于某个存储空间。
Object:对象/文件。对象由元信息(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的Key来标识。
Region:地域,即OSS数据中心所在物理位置。数据存储的地域根据费用、请求来源等来综合选择。
Endpoint:访问域名。形式为HTTP RESTful API。访问不同地域需要不同域名,内外网访问同一个地域也需要不同域名。
AccessKey:访问密钥,简称AK,包括AccessKeyId(用于标识用户)和AccessKeySecret(用于签名字符串加密和验证,必须保密)。
使用
实现方式:服务端签名后浏览器直接上传OSS
- 浏览器向应用服务器请求上传Policy,Policy里包含授权信息和上传信息
- 应用服务器返回上传Policy
- 浏览器凭借Policy上传文件到OSS
开通阿里云对象存储
开通地址:对象存储 OSS_云存储服务_企业数据管理_存储-阿里云
管理控制台:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
产品文档:管理控制台-》概览-》常用入口-》API文档-》文档中心打开
创建Bucket
创建RAM用户并赋权
管理控制台-》右上角账号-》AccessKey管理-》开始使用子用户AccessKey-》创建用户-》填写登录名称(gulimall)、显示名称(gulimall),勾选OpenAPI 调用访问-》确定-》记住AccessKey ID和AccessKey Secret(只在创建时显示,不支持后续查看)-》勾选用户-》添加权限-》选择权限如AliyunOSSFullAccess-》确定-》完成
SDK示例
引入依赖参见:产品文档-》SDK示例-》Java-》Java安装
上传文件见:产品文档-》SDK示例-》Java-》Java对象/文件-》Java上传文件-》Java简单上传-》上传文件流
创建第三方服务模块
项目类型:Spring Initializr
元数据:
Group:com.atguigu
Artifact:gulimall-third-party
Type:Maven
Language:Java
Packaging:Jar
Java Version:8
Description:谷粒商城-第三方服务
Package:com.atguigu.gulimall.thirdparty
(Version默认,Name跟填Artifact)
依赖选择界面,Sping Boot版本选2.x的,依赖选择Spring Web、OpenFeign后点Next-》Finish
检查、修改pom:
a.检查java.version是否为1.8,若为17,说明依赖选择界面Sping Boot版本选错了,要删除该模块重建
b.spring-boot-starter-parent的<version>改为2.1.8.RELEASE
c.spring-cloud.version改为Greenwich.SR3
修正GulimallThirdPartyApplicationTests代码
引入gulimall-common依赖,在启动类注解添加exclude属性来禁止SpringBoot自动注入数据源配置:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
引入alicloud-oss依赖
引入alicloud-oss及spring-cloud-alibaba-dependencies:
<dependencies>
其他<dependency>...
<!--aliyun-oss-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
其他<dependency>...
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
添加到注册中心和配置中心
新增命名空间third-party,并在该命名空间下添加配置application.properties和oss.properties:
application.properties:
server.port=30000
spring.application.name=gulimall-third-party
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
oss.properties:
spring.cloud.alicloud.access-key=<你的ak>
spring.cloud.alicloud.secret-key=<你的sk>
spring.cloud.alicloud.oss.endpoint=<你的endpoint,不要带https://,否则后面服务器签名时拼接host麻烦>
spring.cloud.alicloud.oss.bucket=gulimall-tongwx
third-party模块/src/main/resources/bootstrap.properties:
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=0bb35f66-d0ba-4922-978c-8715bfb1037a
spring.cloud.nacos.config.prefix=application
spring.cloud.nacos.config.ext-config[0].data-id=oss.properties
spring.cloud.nacos.config.ext-config[0].refresh=true
third-party模块启动类中使用@EnableDiscoveryClient开启服务注册发现功能
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
添加到网关
gulimall-gateway\src\main\resources\application.properties:
spring.cloud.gateway.routes[1].id=third_party_route
spring.cloud.gateway.routes[1].uri=lb://gulimall-third-party
spring.cloud.gateway.routes[1].predicates[0]=Path=/api/thirdparty/**
spring.cloud.gateway.routes[1].filters[0]=RewritePath=/api/(?<segment>/?.*), /${segment}
测试应用服务器上传OSS
@Autowired |
使用服务端签名后浏览器直传OSS
third-party模块提供服务端签名接口:
package com.atguigu.gulimall.thirdparty.controller; |
前端renren-fast-vue模块代码
- 将资料中的前端组件upload文件夹拷贝到src\components目录下
- 修改src\components\upload目录下multiUpload.vue和singleUpload.vue中的el-upload元素的action值为自己的Bucket域名
- 确认src\components\upload\policy.js中url定义的后端接口无误
- src\views\modules\product\brand-add-or-update.vue中:
- 导入singleUpload组件:
import SingleUpload from "@/components/upload/singleUpload"; - 注册singleUpload组件:export default下增加:
components: { SingleUpload }, - 使用singleUpload组件:把<el-form-item label="品牌logo地址">中的<el-input>替换为:
<single-upload v-model="dataForm.logo"></single-upload>
- 导入singleUpload组件:
OSS开启允许跨域上传
管理控制台-》Bucket列表-》要设置的Bucket-》数据安全-》跨域设置-》创建规则-》配置来源、Methods、Headers等-》确定
测试
renren-fast-vue前端页面登录-》商品系统-》品牌管理-》新增
注:如果没有新增按钮,可临时修改源代码src/utils/index.js中的isAuth方法直接return true;
定义一个统一返回对象包装类:
package com.tongwx.demo.config; |
定义一个统一返回注解,用于注解要使用统一返回的Controller类或其方法:
package com.tongwx.demo.config; |
定义一个实现ResponseBodyAdvice接口的统一返回通知类:
package com.tongwx.demo.config; |
返回String类型的包装需要特殊处理:
package com.tongwx.demo.config; |
在common模块定义一个错误码枚举:
package com.atguigu.common.enums;
/**
* 错误码枚举
* 定义规则:5为数字,前两位表示业务场景,后三位表示错误码
* 例如:100001。10:通用场景 001:未知错误
* 场景列表:
* 10: 通用场景
* 11: 商品场景
* 12: 订单场景
* 13: 购物车场景
* 14: 物流场景
* 15:用户场景
* 21:库存场景
**/
public enum ErrCodeEnum {
UNKNOWN(10000,"未知异常"),
ARG_NOT_VALID(10001,"参数错误"),
TO_MANY_REQUEST(10002,"请求流量过大,请稍后再试"),
TO_MANY_SMS_CODE(10002,"验证码获取频率太高,请稍后再试"),
PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
USER_EXIST(15001,"存在相同的用户"),
PHONE_EXIST(15002,"存在相同的手机号"),
LOGIN_EXCEPTION(15003,"账号或密码错误"),
NO_STOCK(21000,"商品库存不足"),
;
private Integer code;
private String message;
ErrCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
在分模块如product模块定义一个全局异常通知
@RestControllerAdvice等于@ResponseBody + @ControllerAdvice
@RestControllerAdvice可使用basePackages属性指定作用域
package com.atguigu.gulimall.product.exception;
import com.atguigu.common.enums.ErrCodeEnum;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 统一异常处理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
/**
* 参数校验异常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R errorHandlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {
Map<String, String> errMap = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(fieldError -> errMap.put(fieldError.getField(), fieldError.getDefaultMessage()));
return R.error(ErrCodeEnum.ARG_NOT_VALID.getCode(), ErrCodeEnum.ARG_NOT_VALID.getMessage()).put("data", errMap);
}
/**
* 运行时异常
*/
@ExceptionHandler(value = RuntimeException.class)
public R errorHandlerRuntimeException(RuntimeException ex) {
return R.error(ErrCodeEnum.RUNTIME_EXCEPTION.getCode(), ex.getMessage());
}
/**
* 其他异常
*/
@ExceptionHandler(value = Throwable.class)
public R errorHandler(Throwable ex) {
if(ex.getMessage() != null && (
ex.getMessage().startsWith("JSON parse error: parseDecimal error") ||
ex.getMessage().startsWith("JSON parse error: parseInt error"))){
return R.error(ErrCodeEnum.ARG_NOT_VALID.getCode(), "数字转换异常,请确保提交的数字类型参数中没有非数字");
}
log.error(ex.getMessage(), ex);
return R.error(ex.getMessage());
}
}
-
- Validation
Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。
hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。
引入依赖
如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。
如果spring-boot版本大于2.3.x,则需要手动引入依赖。
本项目gulimall-common模块不是spring项目,后文在gulimall-common模块自定义校验注解时,需要引入依赖。
定义分组规则
在common模块com.atguigu.common.valid包中定义分组规则:
package com.atguigu.common.valid; | package com.atguigu.common.valid; | package com.atguigu.common.valid; |
在DTO字段上声明约束注解
javax.validation.constraints提供的约束注解: | org.hibernate.validator.constraints提供的约束注解: | ||
@Null | 必须为null | @Range(min=,max=) | 必须在合适的范围内 |
@NotNull | 必须不为null | @Length(min=,max=) | 字符串的大小必须在指定的范围内 |
@NotEmpty | 字符串不能为null或"" 集合/数组长度必须大于0 | @URL | |
@NotBlank | 字符串不能为null或trim后不能为"" | @UniqueElements | |
@Size(max=,min=) | 集合/数组/字符串长度必须在指定范围内 | @ScriptAssert | |
@AssertTrue | 必须为true | @SafeHtml | |
@AssertFalse | 必须为false | @ParameterScriptAssert | |
@Min(value) | Number或String不能小于指定的最小值 | @Mod10Check | |
@Max(value) | Number或String不能大于指定的最大值 | @Mod11Check | |
@DecimalMin(value) | 必须是数字,其值不能小于指定的最小值 | @LuhnCheck | |
@DecimalMax(value) | 必须是数字,其值不能大于指定的最大值 | @ISBN | |
@Digits(integer, fraction) | 必须为数字,且 整数位数必须等于integer参数值, 小数位数必须等于fraction参数值。 | @EAN | |
@Positive | 必须为正数 | @Currency | |
@PositiveOrZero | 必须为正数或0 | @CreditCardNumber | |
@Negative | 必须为负数 | @ConstraintComposition | |
@NegativeOrZero | 必须为负数或0 | @CompositionType | |
@Past | 必须是过去的日期 | @CodePointLength | |
@PastOrPresent | 必须是现在或过去的日期 | @DurationMax | |
@Future | 必须是将来的日期 | @DurationMin | |
@FutureOrPresent | 必须是现在或将来的日期 | @NIP | |
@Pattern(regex=,flag=) | 必须符合指定的正则表达式 | @PESEL | |
| 必须是电子邮箱地址 | @REGON | |
@CNPJ | |||
@CPF | |||
@TituloEleitoral |
约束注解的核心属性:
message属性:如果约束注解不指定message属性,那么异常信息会从应用类路径下的ValidationMessages.properties文件中加载,如果没有,再从依赖包中的同名文件中加载。
groups属性:用于分组校验功能。
payload属性:
示例:在product模块的com.atguigu.gulimall.product.entity.BrandEntity中声明约束注解并指定message和groups属性:
package com.atguigu.gulimall.product.entity;
import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.ListValue;
import com.atguigu.common.valid.UpdateGroup;
import com.atguigu.common.valid.UpdateStatusGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* 品牌
*
* @author tongwx
* @email 540078659@qq.com
* @date 2023-03-12 04:38:05
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class,UpdateGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "品牌logo地址不合法",groups={AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
// @Pattern()
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups={AddGroup.class})
@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups={AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
private Integer sort;
}
在方法参数上声明校验注解
校验注解有@Valid和@Validated。@Valid是validation-api的注解,@Validated是Spring框架的注解。@Validated是对@Valid的封装。
如果不使用分组校验功能,校验注解使用@Valid或@Validated都可以。
如果要使用分组校验功能,则需使用Spring框架的@Validated注解。
当@Validated注解未指定分组规则且对应参数为null时,只有未定义groups属性的空/非空校验注解会进行校验。
当@Validated注解未指定分组规则且对应参数非null时,只有未定义groups属性的校验注解会进行校验。
当@Validated注解指定了分组规则且对应参数为null时,只有groups属性匹配的空/非空校验注解会进行校验。
当@Validated注解指定了分组规则且对应参数非null时,只有groups属性匹配的校验注解会进行校验。
注:空/非空校验注解指:@Null/@NotNull/@NotEmpty/@NotBlank
在product模块的com.atguigu.gulimall.product.controller.BrandController的方法参数上声明校验注解,并指定分组规则:
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
/**
* 修改状态
*/
@RequestMapping("/update/status")
public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
除了在方法参数上声明校验注解,还可以使用验证器对象来校验:
// 验证器对象
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// 验证并返回验证结果
private Set<ConstraintViolation<BrandEntity>> set = validator.validate(brandEntity);
// 分组验证并返回验证结果
private Set<ConstraintViolation<BrandEntity>> set = validator.validate(brandEntity, AddGroup.class);
在统一异常处理器中增加校验失败处理逻辑
如果校验失败,会抛出MethodArgumentNotValidException异常,Spring默认会返回400响应码(Bad Request)。
com.atguigu.gulimall.product.exception.GlobalExceptionAdvice中增加异常处理逻辑:
/**
* 参数校验异常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R errorHandlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {
Map<String, String> errMap = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(fieldError -> errMap.put(fieldError.getField(), fieldError.getDefaultMessage()));
return R.error(ErrCodeEnum.ARG_NOT_VALID.getCode(), ErrCodeEnum.ARG_NOT_VALID.getMessage()).put("data", errMap);
}
自定义校验
本例使用自定义校验来约束BrandEntity的showStatus属性只允许提交0或1两种值。
gulimall-common模块不是spring项目,gulimall-common模块自定义约束注解时,需要引入依赖(引入其中一个即可)。
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.1.Final</version> </dependency> | <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> |
编写自定义约束注解com.atguigu.common.valid.ListValue:
message、groups、payload为必须定义的属性
message的默认值key一般取message的完全限定名,value定义在类路径下的ValidationMessages.properties文件中
vals属性用来指定允许的值
@Constraint的validatedBy属性指定校验器,可指定多个
package com.atguigu.common.valid; |
编写自定义错误信息配置gulimall-common\src\main\resources\ValidationMessages.properties:
key为约束注解的message属性中定义的key
com.atguigu.common.valid.ListValue.message=必须提交指定的值
编写自定义校验器com.atguigu.common.valid.ListValueConstraintValidator:
即ListValue中@Constraint的validatedBy属性指定的校验器
package com.atguigu.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 判断是否校验成功
* @param value 需要校验的值
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
将自定义约束注解应用到com.atguigu.gulimall.product.entity.BrandEntity#showStatus:
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
其他功能
级联验证:通过@Valid注解实现级联校验。举个例子,我的ScriptionBO中有一个List属性。我希望Validation框架在校验ScriptionBO的时候,不仅仅校验ScriptionBO的属性,还要验证其中List涉及的User们。那么在List上添加@Valid注解,就可以实现了。
分组序列:通过分组校验,再加上@GroupSequence({xxxGroup.class,xxxGroup.class}),就可以实现分组序列了。举个例子,登录场景下,User连userId的非空校验都没有通过,那么就更不需要校验手机号码,邮箱等。
创建项目
创建maven项目:
File-》New-》Project...-》Maven-》Next-》...
JKD配置:
File-》Project Structure(新建空白项目后会自动弹出)-》Project Settings-》Project-》Project SDK-》选1.8以上
Maven配置:
File-》Build,Execution,Deployment-》Build Tools-》Maven-》User settings file-》override打勾-》选择自定义镜像如阿里云的settings.xml
如果是克隆已有项目:
File-》New-》Project from Version Control-》Git-》...
创建模块
注:通用模块创建为maven工程,业务模块创建为Spring Boot工程
创建父工程(打包方式pom)
父工程管理依赖版本
创建接口工程
接口工程定义业务接口和数据库实体类,各业务相关工程引用接口工程,实现业务接口方法。
创建公共工具类工程
创建service工具类工程
创建web工具类工程
创建用户服务工程
项目右键-》New-》Module-》Spring Initializr-》元数据见下表-》起步依赖勾选web、mysql、jdbc、Mybatis-》...
<groupId>com.tongwx.mall</groupId> |