文章目录
- 前言
- 一、项目环境搭建
* - 1.1、安装virtualbox以及vagrant
- 1.2、Docker安装MySQL与Redis
- 1.3、前后端开发工具统一配置
- 1.4、Git工具安装与配置
- 1.5、Gitee创建仓库与IDEA导入
- 1.6、构建微服务模块
- 1.7、编写.gitignore文件(忽略上传gitee文件配置)
- 1.8、数据库初始化
- 1.9、逆向工程(生成代码)
– - 二、搭建分布式环境
* - 前提:gulimall-common进行springcloud alibaba版本控制依赖
- 2.1、集成Nacos注册中心
– - 2.2、集成Openfeign组件
- 2.3、集成Nacos配置中心
- 2.4、集成网关服务
- 三、商品服务
* - 3.1、三级分类API
–- 三级分类介绍
- 3.1.1、查询—产品分类递归树型结构数据获取(后端服务)
- 3.1.2、配置管理服务renren-fast网关路由(后端+前端配置)
+ - 后端服务(网关动态路由、集成跨域服务)
- 前端服务
- 测试
- 3.1.3、产品分类查询(前端)
+ - 前端产品分类页面创建
- 配置商品服务gulimall-product的动态路由地址
- 前端产品分类接口实现(集成3.1.1查询接口)
- 3.1.4、删除单个分类(后端+前端实现)
+ - 效果展示及思路
- 后端代码(逻辑删除)
- 前端代码
- 3.1.5、新增单个分类(后端+前端实现)
+ - 效果展示及思路
- 后端代码
- 前端代码
- 3.1.6、编辑单个分类(后端+前端实现)
+ - 效果展示及思路
- 后端代码
- 前端代码
- 3.1.7、拖拽分类并实现批量保存(后端+代码实现)
+ - 效果展示及思路
- 后端代码
- 前端代码
- 3.1.8、批量删除分类(后端+代码实现)
+ - 效果展示及思路
- 后端代码
- 前端代码
- 3.2、品牌管理API
–
前言
目前博主正在学习谷粒商城项目中,正在不断更新中…
所有博客文件目录索引:博客目录索引(持续更新)
一、项目环境搭建
1.1、安装virtualbox以及vagrant
在virtualbox创建centos7环境使用的是vagrant虚拟运行环境管理工具来进行构建的,可见该篇博客:虚拟运行环境管理工具Vagrant详细使用教程。
效果如下:
使用vagrant构建的centos7环境,默认的root没有设置密码,我们需要进行设置:
sudo passwd root
快速构建Docker环境可见我之前博客(环境安装部分):快速使用Docker部署MySQL、Redis、Nginx
1.2、Docker安装MySQL与Redis
Docker构建MySQL:
在 /mydata/mysql/conf
目录下创建 mysqld.cnf
文件:
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
symbolic-links=0
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
接着去拉取镜像并启动:
docker pull library/mysql:5.7.36
docker run -p 3306:3306 --name mysql \
-v /etc/localtime:/etc/localtime \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql/mysql.conf.d \
-e MYSQL_ROOT_PASSWORD=root \
-d library/mysql:5.7.36
docker exec -it mysql /bin/bash
mysql -uroot -proot --default-character-set=utf8
grant all privileges on *.* to 'root' @'%' identified by 'root';
flush privileges;
Docker构建Redis:
docker pull redis:5
在mydata/redis目录下创建 redis.conf
:文件如下
链接:https://pan.baidu.com/s/1tT3SWnnL0OylWA5So_V9Mw
提取码:9xko
其中配置已经修改了,包含如下:
appendonly yes:开启持久化
启动redis镜像,关键是设置其中的密码:
docker pull redis:5
docker run --log-opt max-size=100m --log-opt max-file=2 \
-p 6379:6379 --name redis \
-v /etc/localtime:/etc/localtime \
-v /mydata/redis/redis.conf:/etc/redis/redis.conf \
-v /mydata/redis/data:/data \
-d redis:5 redis-server /etc/redis/redis.conf \
--appendonly yes \
--requirepass 123456
docker exec -it redis redis-cli
1.3、前后端开发工具统一配置
后端
后端开发工具:idea
maven配置:Maven快速配置—配置文件
idea插件安装:lombok、mybatisx、Gitee、jrebel(Java代码修改无需重启)插件和ResetfulTool(一套 RESTful 服务开发辅助工具集)
前端
前端:vscode
安装插件:
Vetur —— 语法高亮、智能感知、Emmet 等
包含格式化功能, Alt+Shift+F (格式化全文),Ctrl+K Ctrl+F(格式化选中代码,两个 Ctrl
需要同时按着)
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 —— 中文语言包
1.4、Git工具安装与配置
Git的下载与配置可见我的博文(详细):Git详细使用指南(含详细命令、实操)
包含添加密钥到码云。
1.5、Gitee创建仓库与IDEA导入
Gitee创建仓库:
仓库名称、初始化仓库以及选择分支模型(生产/开发模型):
IDEA导入仓库:
创建完成如下:
; 1.6、构建微服务模块
创建模块:商品服务(product)、仓储服务(ware)、订单服务(order)、优惠券服务(coupon)、用户服务(member)
多模块相同组件:Springboot版本2.1.8.RELEASE、SpringCloud版本Greenwich.SR3
1)、引入依赖web、openfeign
2)、每一个服务,包名 com.atguigu.gulimall.xxx(product/order/ware/coupon/member)
3)、模块名:gulimall-xx
分组:com.atguigu.gulimall
坐标:gulimall-xxx
包名:com.atguigu.gulimall.xxx
构建完我们所需要的几个模块如下图(单个模块构建过程往下翻有示例):
在当前目录下创建一个pom.xml(从之前模块中移过来),进行统一管理模块:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.atguigu.gulimallgroupId>
<artifactId>gulimallartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>gulimallname>
<description>谷粒商城聚合服务description>
<packaging>pompackaging>
<modules>
<module>gulimall-couponmodule>
<module>gulimall-membermodule>
<module>gulimall-ordermodule>
<module>gulimall-productmodule>
<module>gulimall-couponmodule>
modules>
project>
如何导入这个聚合模块呢?右边Maven+号添加这个pom.xml即可:
此时就会出现一个总的管理root:
创建gulimall-product示例(版本说明)
创建商品模块:
组件选择:
- 后来修正为视频一样的版本2.1.8
注意一下springboot与springcloud的版本:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.8.RELEASEversion>
<relativePath/>
parent>
<spring-cloud.version>Greenwich.SR3spring-cloud.version>
1.7、编写.gitignore文件(忽略上传gitee文件配置)
上面模块都构建好了之后,我们可以看到当前的版本控制管理有134个文件没有被管理:
部分文件如.idea文件下的、target目录等我们在代码完成之后无需上传代码仓库,此时就需要在当前目录下的.gitignore中进行配置:
一些配置信息如下:
**/.gitignore
**/mvnw
**/mvnw.cmd
**/.mvn
.idea
**/target
*.iml
rebel.xml
添加到版本控制操作:
添加版本控制(add):
提交到本地仓库(及push推送)
OK,此时我们看下Gitee仓库状态,所有的代码已经进行了上传:
1.8、数据库初始化
下载安装数据库表设计工具
PowerDesigner16.5:PowerDesigner安装+破解+汉化、PowerDesigner 安装破解
链接:https://pan.baidu.com/s/1HoifsS4ywFjyNO-jyhZy5A
提取码:ihrj
数据库设计源文件
链接:https://pan.baidu.com/s/1Jv3ImyMHQEpay5RzwUK9Ug
提取码:p2on
下面是五个服务的表:
创建数据库与表
对应每一个微服务都操作一个数据库
oms 订单数据库
pms 商品数据库
sms 营销数据库
ums 用户数据库
wms 库存数据库
以商品系统为准,创建utf8mb4字符集的数据库:
依次创建好数据库之后导入预先准备的sql( 注意不要走navicat的直接导入可能会出现字段提示乱码问题,我们需要打开某个sql全选后在GUI中复制执行!):
sql文件
链接:https://pan.baidu.com/s/1IE12fJsyS6THG9xug1nmBg
提取码:f4qr
1.9、逆向工程(生成代码)
对于代码生成我们使用的是人人开源的项目:https://gitee.com/renrenio
- renren-fast作为后台管理系统后端服务
- renren-fast-vue作为后台管理系统页面
- renren-generator进行代码生成
; 1.9.1、renren-fast快速构建后端管理页面及服务
后端系统服务
renren-fast开源地址:https://gitee.com/renrenio/renren-fast
将renren-fast添加到gulimall目录下:
数据库创建gulimall_admin</code>,并且导入项目中的db目录下的mysql.sql:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/cc95ff0d53224b513512d4c6422c0f2b.png" /></p>
<p>将该模块项目导入到IDEA中,接着我们修改renren-fast中的application.yml文件中的数据库连接地址和用户名与密码:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/e166474aa3000cf6b167103e147ffb33.png" /></p>
<p>接着启动项目,启动成功如下图所示:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/77e0b251f32f64ef8a7a1b166a37498a.png" /></p>
<p>在网页上进行测试一下:http://localhost:8080/renren-fast/</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/8b6d7a4a2a7099497e25fcd1e301f314.png" /></p>
<p>为什么会有renren-fast后缀?主要是在application.yaml中配置了 <code>server.servlet.context-path=/renren-fast</code>。</p>
<blockquote>
<p>前端服务</p>
</blockquote>
<p>renren-fast-vue开源地址:https://gitee.com/renrenio/renren-fast-vue</p>
<p>下载克隆下来之后我们进入根目录下输入命令去导入依赖:</p>
<ul>
<li>关于node.js下载与npm包配置可见我的博客:<a href="https://blog.csdn.net/cl939974883/article/details/124680367?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22124680367%22%2C%22source%22%3A%22cl939974883%22%7D&ctrtid=oY1jN">Node.js学习笔记 认识Node.js以及npm使用</a></li>
</ul>
<pre><code class="language-shell">npm install
</code></pre>
<p>全部添加成功如下图所示:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/cb0aa53bdc566118cc6e635e047bd3e0.png" /></p>
<p>你可以在config目录下index.js里看到已经配置好了对应我们renren-fast也就是admin后台系统的api地址,我们无需进行改动:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/9714579d655365774b724409c744a7bb.png" /></p>
<p>接着去运行命令启动前端服务并进行测试访问下后台管理系统:</p>
<pre><code class="language-shell">npm run dev
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/2110a0deb85dbfb000f7077ef8dbe738.png" /></p>
<p>启动成功之后我们来访问下该页面:http://localhost:8001/</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/44ed6027be1a6cc3fd865024d55a5811.png" /></p>
<pre><code class="language-shell">
admin
admin
</code></pre>
<p>成功登录后的效果如下所示:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/7e48a6aa3d364cd73df4013d34ee2562.png" /></p>
<h3>1.9.2、renren-generator快速生成微服务业务代码(营销服务、用户服务、订单服务、商品服务、库存服务)</h3>
<h4>最终生成模块展示</h4>
<p>renren-generator开源地址:https://gitee.com/renrenio/renren-generator</p>
<p>下载克隆下来之后添加到gulimall中</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/5684c0bc3277b47d96692ab39117a4a7.png" /></p>
<p>这个项目实际上提供了一个web服务,其能够连接对应的数据库通过页面中勾选指定的数据库来生成预先配置好的后端前端代码。</p>
<p>对应逆向生成代码的操作可见下面示例章节!</p>
<p>五个模块对应我们设置的开放端口为:</p>
<pre><code> 微服务模块 端口 数据库表 注释
gulimall-coupon 7000 gulimall_sms 营销数据库 √
gulimall-member 8000 gulimall_ums 用户数据库 √
gulimall-order 9000 gulimall_oms 订单数据库 √
gulimall-product 10000 gulimall_pms 商品数据库 √
gulimall-ware 11000 gulimall_wms 库存数据库 √
</code></pre>
<p>初步搭建好的服务模块如下,右边的RestServices包含所有的服务:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/d0ef0cf27b0eecdeb0b1c10a3a75194c.png" /></p>
<p>当前构建的环境代码:</p>
<pre><code>链接:https://pan.baidu.com/s/1g0E-cbXrhLZPGIg0z83ykA
提取码:ep7s
</code></pre>
<h4>示例:在gulimall-product模块中添加逆向工程代码</h4>
<p>application.yml:修改连接数据库的ip地址与数据库名、用户名与密码</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/2fe49eaaa3973ef5f98a1b9398fa1b1f.png" /></p>
<p>generator.properties:代码生成的配置项</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/93cb6b268e26e911fab657ec24bab952.png" /></p>
<ul>
<li>这个 <code>mainPath</code>指的是引入的自定义工具类的包路径,如PageUtils、R等等。</li>
</ul>
<p>启动该服务:http://localhost:80</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/44890beb522da56203407fb5d839b6a8.png" /></p>
<p>进入到网页之后,我们需要进行选中所有的表,然后进行生成代码: <strong>注意可能一个数据库会超过10条,那么我们选择一页显示50条,然后统一勾选</strong></p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/1c583e874776da77f2a0d86baa5ba0ef.png" /></p>
<p>生成代码的目录如下:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/968fdbf3a9cb697327f8b159c09da5bb.png" /></p>
<p>我们将整个 <code>main</code>目录复制到 <code>gulimall-product</code>工程的src目录下(去除掉resources下的src目录也就是前端代码):</p>
<p>此时我们能够很明显的看到工程目录下java代码文件都是爆红,主要原因就是生成的代码中引入了一些当前工程没有引入的依赖(对于这这类可以生成不同模块的代码我们单独使用一个common包来进行引入一些第三方依赖):</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/0ae43f5f4888cc15a3b84d7c00f276eb.png" /></p>
<p><strong>对于生成的代码解决方案</strong>:</p>
<p>1、构建gulimall-common模块,引入生成代码所必须的一些工具类。</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/bf3fd795efece3f0e785a78996b6b74a.png" /></p>
<pre><code>当前的gulimall-common工程包
链接:https://pan.baidu.com/s/1DZ0vtrAyHTFnHZue2rzTOg
提取码:8mvi
</code></pre>
<p>pom.xml需要引入的第三方类:</p>
<pre><code class="language-xml"><dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.17version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
dependencies>
</code></pre>
<p>②修改代码生成器中的controller代码模板(去除掉controller部分的shiro权限代码,谷粒商城将会使用的是springsecurity)</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/e0f28b2cc41e1db44ff40d4a2b4fd031.png" /></p>
<p>2、针对当前模块自定义配置准备</p>
<p>上面两步是针对之后所有逆向生成模块的额外步骤,接着我们来针对某个模块来进行配置,我们重新生成一下product代码放入到product模块中,接着做如下操作:</p>
<p>①pom.xml引入gulimall-common</p>
<pre><code class="language-xml">
<dependency>
<groupId>com.atguigu.gulimallgroupId>
<artifactId>gulimall-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
</code></pre>
<p>②使用renren-generator来生成对应服务的逆向代码直接复制到src目录下</p>
<p>③在SpringBoot启动器上添加MapperScan包扫描路径</p>
<pre><code class="language-java">@MapperScan("com.atguigu.gulimall.product.dao")
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/e59457eac76a4cdf96ddce7570c30f8f.png" /></p>
<p>④编写配置 <code>application.yml</code>,主要数据库源以及mp的配置:</p>
<pre><code class="language-yaml">server:
port: 10000
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.3.137:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
</code></pre>
<p>二、搭建分布式环境</p>
<h2>前提:gulimall-common进行springcloud alibaba版本控制依赖</h2>
<p>根据当前使用的springboot版本、Springcloud版本来选择对应的springcloud alibaba版本:<a href="https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E">Alibaba-SpringCloud Alibaba 版本说明</a></p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/4060d7c4094a169bbd604c94cd29d64e.png" /></p>
<p>对于2.1版本的springboot,我们需要选择2.1.2的springcloud alibaba,在谷粒商城视频里选择的是2.1.0,那我们这也就选择使用2.1.0,将其添加到 <code>gulimall-common</code>模块中:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/53f2e74903ea904ca16ae3cf74cb6439.png" /></p>
<pre><code class="language-xml"><dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
</code></pre>
<h2>2.1、集成Nacos注册中心</h2>
<h3>gulimall-common统一引入注册发现依赖</h3>
<p>由于每一个服务都需要添加到注册中心,那么将nacos注册发现统一集成到common模块当中:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/6750d446cb9017a38a3b21c2af329ea8.png" /></p>
<pre><code class="language-xml">
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
</code></pre>
<h3>启动Nacos-server服务</h3>
<p>本地启动nacos服务,下载nacos-server1.1.3:https://github.com/alibaba/nacos/releases?page=4</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/f7c32add80975e28df988b8ba85d210f.png" /></p>
<pre><code class="language-shell">链接:https://pan.baidu.com/s/12R8-kB6drpWSxmrnJ4gwvQ
提取码:xgct
</code></pre>
<p>进入到bin目录,直接双击startup.cmd来进行启动:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/c06db270d33ff1454182a6c79350f8e6.png" /></p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/c96bace8d7aa3623db63bfbbbd7e2714.png" /></p>
<p>nacos访问服务端口默认是在8848端口:http://localhost:8848/nacos/</p>
<pre><code>nacos
nacos
</code></pre>
<h3>所有模块服务集成注册发现</h3>
<p>最终集成的效果:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/09d68abc6867ab119e9a205a3c8c61a8.png" /></p>
<p>查看下最终的nacos注册中心:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/da07ba7949cf04ea23f3f52526996437.png" /></p>
<blockquote>
<p>示例:guli-coupon集成过程</p>
</blockquote>
<p>①配置application.yaml</p>
<p>在应用的 /src/main/resources/application.yaml配置文件中配置 Nacos Server 地址以及application.name也就是应用名称(才能够注册上):</p>
<pre><code class="language-yaml">spring:
application:
name: gulimall-coupon
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
</code></pre>
<p>②在启动器上开启服务注册发现注解:</p>
<pre><code class="language-java">@EnableDiscoveryClient
</code></pre>
<p>启动gulimall-coupon服务:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/08ba94f93f02418ca0fccefeaf4147df.png" /></p>
<p>查看下nacos注册中心是否已经注册上:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/9e48165091e259e5fc08f46a7fb364ec.png" /></p>
<h2>2.2、集成Openfeign组件</h2>
<p>实战目标:在营销服务中编写一个接口,接着在用户服务中编写一个用户优惠券接口,在这个接口中我们将会进行远程调用营销服务中的接口并进行返回(openfeign完成远程调用)。</p>
<p>①在gulimall-coupon服务中添加一个接口</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/2b2ecf5cd9002cdf33c5a4afd706f505.png" /></p>
<pre><code class="language-java">@RequestMapping("/member/list")
public R memberCoupons() {
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10");
return R.ok().put("coupons", Arrays.asList(couponEntity));
}
</code></pre>
<p>②在gulimall-member服务中编写远程调用接口</p>
<p>首先需要给gulimall-member服务中集成openfeign并进行开启feign包扫描进行加强:</p>
<p>pom.xml引入:</p>
<pre><code class="language-xml">
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
</code></pre>
<p>编写对应的feign接口:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/5c325098ec868dcb1bc8731e3422174d.png" /></p>
<pre><code class="language-java">package com.atguigu.gulimall.member.feign;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
@RequestMapping("/coupon/coupon/member/list")
public R memberCoupons();
}
</code></pre>
<p>在启动器上添加自动包扫描:</p>
<pre><code class="language-java">@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
</code></pre>
<p>最后就是在控制器上添加一个接口其中就包含远程调用:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/6d9866dd1ce37044e728e2586713238f.png" /></p>
<pre><code class="language-java">@Autowired
CouponFeignService couponFeignService;
@RequestMapping("/coupons")
public R test() {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("长路");
R membercoupons = couponFeignService.memberCoupons();
return R.ok().put("member", memberEntity).put("coupons", membercoupons.get("coupons"));
}
</code></pre>
<p>测试下接口:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/d623685ae3158cdd4a38c4bab3221251.png" /></p>
<h2>2.3、集成Nacos配置中心</h2>
<blockquote>
<p>介绍</p>
</blockquote>
<p>命名空间:可基于环境进行隔离、也可以根据单个微服务来进行隔离。</p>
<p>配置集:指的就是单个命名空间下对应的group分组,场景如:单个服务在双11、618来进行分组隔离。</p>
<blockquote>
<p>实战</p>
</blockquote>
<p>由于我们的所有微服务都需要使用到配置中心,所以我们需要在gulimall-common模块中进行配置:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/91a943e644bb628da5987a1e02ab8205.png" /></p>
<pre><code class="language-xml">
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
</code></pre>
<p>接着我们就需要在对应需要使用配置中心配置信息的服务模块中添加bootstrap.properties文件:</p>
<p><strong>遵守的规则如下</strong>:</p>
<ul>
<li>当前谷粒商城规范:按照每个微服务来创建自己的命名空间,接着使用配置分组来区分环境如dev、test、prod。</li>
<li>加载多配置集:对数据源、框架相关的配置来统一拆分成不同的配置文件。</li>
</ul>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/fe77014932a2fed2c3f63eb07b445d15.png" /></p>
<pre><code class="language-properties">spring.application.name=gulimall-coupon
nacos配置中心地址(若是只有一条,默认namespace是public, Group是DEFAULT_GROUP,data-id是gulimall-coupon.properties)
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=cf8005b8-b2c3-47cd-b344-18661e5cba70
spring.cloud.nacos.config.group=prod
多配置级,将初始配置来进行拆分
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true
</code></pre>
<p>对应在nacos管理面板上,我们需要进行添加一个单独服务的命名空间,并且去配置对应的配置文件其中包含有多环境的以及多配置信息文件:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/fd7fb2050a5ed21dca29f7c58d1368cd.png" /></p>
<ul>
<li>多环境:gulimall-coupon.properties。</li>
<li>多配置(拆分):数据库、框架、单个微服务的相关配置。</li>
</ul>
<pre><code class="language-shell">
coupon.user.name=changlu-dev
coupon.user.age=29
coupon.user.name=changlu-prod
coupon.user.age=28
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.3.137:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
server:
port: 7000
spring:
application:
name: gulimall-coupon
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
</code></pre>
<p>对于上面多环境配置设置的目的就是我们当前来进行测试,对应测试的代码在controller中:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/ad8b217f3f335d5624ec525cf4b0faab.png" /></p>
<p>启动nacos与gulimall-coupon来进行测试dev与pro环境:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/2a3be5cad662f055f0083e5d8feb5357.png" /></p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/d87030b7fb100b7d22808784aa3c00d0.png" /></p>
<p>提示:上线之后我们可以配到配置中心去,线下写代码就直接配在本地即可(由于我使用了RestServices插件,读取配置中心的话对应的一些端口就需要我们自己去手动修改,同时也会很麻烦)。</p>
<h2>2.4、集成网关服务</h2>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/9e7616bf059aae685bd144f620a19b5a.png" /></p>
<p>创建guilimall-gateway模块:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/eca995f632a7dba820c457c220826a10.png" /></p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/820756a8f7c76b9fec896a4056caed43.png" /></p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/5e7f76409a80cdac6811656ca1345e26.png" /></p>
<p>①pom.xml中引入公共模块</p>
<pre><code class="language-xml">
<dependency>
<groupId>com.atguigu.gulimallgroupId>
<artifactId>gulimall-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
</code></pre>
<p>由于引入了common模块,在common模块中还带了mybatis的依赖,所以需要对其进行排除(在启动器上):</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/cd7c85ebd85887b385dee7fa587d23d6.png" /></p>
<p>②排除common组件的依赖</p>
<pre><code class="language-java">
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
</code></pre>
<p>③编写注册中心的配置项</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/48912606af2b30501deb5d5705d51038.png" /></p>
<p>bootstrap.properties:</p>
<pre><code class="language-properties">server.port=88
spring.application.name=gulimall-gateway
服务注册
spring.cloud.nacos.discovery.server-addr=localhost:8848
配置中心
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=6a7b6a2f-871b-46a9-9805-7caf19e65b09
</code></pre>
<p>④编写断言匹配url</p>
<p>application.yml:</p>
<pre><code class="language-yaml">spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://www.baidu.com
predicates:
- Query=url,baidu
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/b4778bc7a9c56a6e0010bba0e70f9b67.png" /></p>
<p><strong>对于其中的gateway断言我们进行了处理,之后我们来对其进行测试</strong>:</p>
<pre><code class="language-shell">localhost:88?url=qq
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/506ebc302eed89630abe517176d6f27c.png" /></p>
<pre><code class="language-shell">localhost:88?url=baidu
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/5fb320069c54dc63565e38965ddd0a2c.png" /></p>
<p>三、商品服务</p>
<h2>3.1、三级分类API</h2>
<h3>三级分类介绍</h3>
<p>在一般的电商平台上就包含有三级分类:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/351529fcf4f704c45a584174d33c7024.png" /></p>
<p>在谷粒商城中,三级分类表是属于商品服务数据库中的表:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/0ac4f3ef743fba585991d9f01255af53.png" /></p>
<ul>
<li>核心字段:parent_cid(父类id)、sort(排序字段)</li>
<li>我们的多级分类是根据父类id来进行划分的,一级分类就是为0,对应二级分类的parent_id就是0,同理三级分类的parent_id就是1。</li>
</ul>
<p>默认在表中是没有数据的,我们需要来进行导入一些初始数据:</p>
<p>将课件中的数据sql代码执行:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/a025d0968c35ae3fd7d0211fe2482f86.png" /></p>
<h3><a name="311_1042">;</a> 3.1.1、查询—产品分类递归树型结构数据获取(后端服务)</h3>
<blockquote>
<p>完成效果</p>
</blockquote>
<p>目标:在gulimall-product模块中编写一个产品三级分类的递归数据结构获取的API接口。</p>
<p>接口:http://localhost:10000/product/category/list/tree</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/a9b33e57822e7bfde5aa725c6c61ad92.png" /></p>
<blockquote>
<p>实现过程</p>
</blockquote>
<p>主要代码包含gulimall-product模块下:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/13a87e9522fb0efd302166fb69bd9537.png" /></p>
<p><code>CategoryController.java</code>:控制器,对应产品分类的树型分类接口</p>
<pre><code class="language-java">@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@RequestMapping("/list/tree")
public R list(){
List<CategoryEntity> entities = categoryService.listWithTree();
return R.ok().put("data", entities);
}
}
</code></pre>
<p><code>CategoryEntity</code>:产品分类实体中添加一个children集合属性,用于封装直接响应给前端,但是注意了这个属性我们需要加上@Field注解表示其不是数据库表中自带的属性,避免在使用mybatisplus查询时出现异常</p>
<pre><code class="language-java">@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
@TableField(exist = false)
private List<CategoryEntity> children;
}
</code></pre>
<p><code>CategoryService.java</code>:定义接口方法</p>
<pre><code class="language-java">public interface CategoryService extends IService<CategoryEntity> {
List<CategoryEntity> listWithTree();
}
</code></pre>
<p><code>CategoryService.java</code>:树型分类业务代码,其中就有涉及到一个递归处理操作</p>
<pre><code class="language-java">package com.atguigu.gulimall.product.service.impl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
@Override
public List<CategoryEntity> listWithTree() {
List<CategoryEntity> entities = baseMapper.selectList(null);
List<CategoryEntity> ans = entities.stream()
.filter((menu) -> menu.getParentCid() == 0)
.map((menu) -> {
menu.setChildren(getChildren(menu, entities));
return menu;
})
.sorted((menu1, menu2) -> {
if (menu1.getSort() == null || menu2.getSort() == null) return 0;
return menu1.getSort() - menu2.getSort();
})
.collect(Collectors.toList());
return ans;
}
public List<CategoryEntity> getChildren(CategoryEntity parent, List<CategoryEntity> all) {
List<CategoryEntity> ans = all.stream()
.filter((menu) -> menu.getParentCid().equals(parent.getCatId()))
.map((menu) -> {
menu.setChildren(getChildren(menu, all));
return menu;
})
.sorted((menu1, menu2) -> {
if (menu1.getSort() == null || menu2.getSort() == null) return 0;
return menu1.getSort() - menu2.getSort();
})
.collect(Collectors.toList());
return ans;
}
}
</code></pre>
<p>其中的这个树型递归流程使用的是stream流来进行处理,filter过滤+map(填充子列表)+sorted(排序)最终使用collect来进行聚合。</p>
<h3>3.1.2、配置管理服务renren-fast网关路由(后端+前端配置)</h3>
<p>对于后台管理系统,原先是通过直接去访问renren-fast的服务地址来进行验证码、登录以及后台管理的一系列操作。</p>
<p>对于分布式项目所有的请求都来通过统一的一个网关来进行路由到不同的服务,在这里我们首先来配置一下网关路由!</p>
<h4>后端服务(网关动态路由、集成跨域服务)</h4>
<p><strong>renren-fast集成配置</strong>:</p>
<p>首先将后台管理服务renren-fast也集成gulimall-common模块,让其也拥有服务注册的能力:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/101772ccef297f97640bba4c431438a1.png" /></p>
<p><code>pom.xml</code>:</p>
<pre><code class="language-xml">
<dependency>
<groupId>com.atguigu.gulimallgroupId>
<artifactId>gulimall-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
</code></pre>
<p>编写一个 <code>bootstrap.properties</code>文件:用于进行服务注册以及配置中心地址配置、应用名</p>
<pre><code class="language-properties">spring.application.name=renren-fast
服务注册
spring.cloud.nacos.discovery.server-addr=localhost:8848
配置中心
spring.cloud.nacos.config.server-addr=localhost:8848
</code></pre>
<p>在启动器上开启服务发现:</p>
<pre><code class="language-java">@EnableDiscoveryClient
</code></pre>
<p><strong>gulimall-gateway集成配置</strong>:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/63d8e3bb255cb1299089ffe07b6f24fc.png" /></p>
<p>准备操作好了之后,我们就来进行 <code>gulimall-gateway</code>服务模块的动态路由部分的编写, <code>application.yml</code>配置如下:</p>
<pre><code class="language-yaml">spring:
cloud:
gateway:
routes:
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?>.*),/renren-fast/$\{segment}
</code></pre>
<p>由于所有的请求都会走我们的网关,那么我们就需要在网关处来进行跨域处理,我们编写一个跨域配置类 <code>GulimallCorsConfiguration</code>:</p>
<pre><code class="language-java">package com.atguigu.gulimall.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
</code></pre>
<p>在网关处集成了跨域请求之后,我们需要去查看下网关路由的那些服务是否也有跨域请求,因为若是其他服务也有跨域请求处理的话,那么就会在响应头部分添加两次允许跨域的响应头信息,在renren-fast服务中就有,我们需要将其进行注释掉:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/fb9df9d1e4533ec942bb46aef11e3c9d.png" /></p>
<p>至此,我们的后端服务就暂且集成完毕!</p>
<h4>前端服务</h4>
<p>对于前端的话无需做很大的改动,只需要将全局的请求路径进行修改即可:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/3aafbe58cddd7f992e675ef27f4c212a.png" /></p>
<pre><code>http://localhost:8080/renren-fast => http://localhost:88/api
</code></pre>
<p>对于后端服务来说,若是匹配到/api/**就会去进行动态路由匹配转发到后端管理服务去进行操作!</p>
<h4>测试</h4>
<p>启动nacos注册中心、网关服务、后台管理服务:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/04b9988f770a1ebe1fe2c74d49c6caab.png" /></p>
<p>此时查看下nacos注册中心,看一下是否已经上线:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/2df69d7dce922daa65813eb2cf527cd6.png" /></p>
<p>没有问题,那么我们就启动前端管理系统项目,来进行一个验证码接口和登录接口的测试:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/c8c96b4eb84d7d4ef333b09cf8666097.png" /></p>
<p>验证码请求:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/443a3fe03ce009f79b927a06062713df.png" /></p>
<p>login登录请求:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/0036714b6f84fd5d519e8054fbdd5e01.png" /></p>
<h3><a name="313_1322">;</a> 3.1.3、产品分类查询(前端)</h3>
<h4>前端产品分类页面创建</h4>
<p>创建目录:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/1c305f6dd47d38e6ede77933f9e40df6.png" /></p>
<p>创建一级菜单:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/47ae16b0dbc7cc165a7e2bcb575bfecf.png" /></p>
<p>看一下当前的分类效果:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/97122e523954771121a67a4fed7263a9.png" /></p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/59ece7926a8ae3db5633242ad56c11a5.png" /></p>
<p>本质实际上就是在sys_menu表中加了两条记录:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/9e2c1a05ab248bf87a2ad55f757ab1c5.png" /></p>
<p>对应 <code>product-category</code>实际就会映射到 <code>product/category.vue</code>这个文件:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/fa9ae8fc0ea5e436a806e8e98fa7b9db.png" /></p>
<h4><a name="gulimallproduct_1350">;</a> 配置商品服务gulimall-product的动态路由地址</h4>
<p>在gulimall-product服务中application.yml中配置商品服务的动态路由:</p>
<pre><code class="language-yaml">spring:
cloud:
gateway:
routes:
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/product/(?>.*),/product/$\{segment}
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/683fd1ef13aac2ea03bea05415a1b5e4.png" /></p>
<p><strong>提示</strong>:你可以看到对于产品服务的动态路由是配置到了renren-fast后台管理服务的路由之上,这是因为renren-fast的路由匹配是从 <code>/api/**</code>就开始的,在gateway中去进行匹配是根据你配置的上下顺来来进行匹配转发的,而我们的商品服务动态路由则是匹配的 <code>/api/product/**</code>,很明显商品服务的路由是后台管理服务的子路由,所以应该需要进行优先匹配,至此需要放置到其上面!</p>
<p>简而言之:精确路由放在高优先级,模糊路由放在低优先级。</p>
<h4>前端产品分类接口实现(集成3.1.1查询接口)</h4>
<p>对于树型组件的展示我们可以直接使用element ui的树型组件:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/822409ef758a63216ae0b4d60bc992d2.png" /></p>
<p>代码如下:</p>
<pre><code class="language-vue">
methods: {
//获取树型菜单数据
getMenus() {
this.$http({
url: this.$http.adornUrl('/product/category/list/tree'),
method: 'get'
}).then(({data}) => {
console.log("成功获取到菜单数据...", data.data)
this.menus = data.data;
})
}
},
// 生命周期 - 创建完成(可以访问当前this实例)
created () {
this.getMenus();
},
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/183b7bd42f750628c37286adca1e9f11.png" /></p>
<p>效果如下:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/7940504af1f68d86d8deec39b65d763f.png" /></p>
<p>接着我们要实现一个前端需求:</p>
<pre><code>每个分类右边都有一个新增与删除的按钮,并且对于新增与删除按钮的显示也有要求:
①新增Append显示要求:只有1级2级分类才具备新增选项。
②删除delete显示要求:只有当子分类数量为0时才允许被删除
</code></pre>
<p>效果如下:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/ae2da9ca3cd8bb34b31bc1faa8e18d4f.png" /></p>
<p>如何能够去添加右边的Append与Delete呢?这就使用到了vue中的插槽语法,修整后的代码如下:</p>
<pre><code class="language-vue">
{{ node.label }}
Append
Delete
</code></pre>
<h3>3.1.4、删除单个分类(后端+前端实现)</h3>
<h4>效果展示及思路</h4>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/5efae0d562614c5a6e7541a1fa11daf3.gif" /></p>
<p>思路逻辑:</p>
<p>1、点击delete,进入message选择框。</p>
<p>2、点击message选择框确认,发起删除请求,最后就是刷新菜单。</p>
<ul>
<li>注意:成功删除之后,原本展开那个分栏框依旧展开。(使用到了el-tree中的default-expanded-keys属性,只需要在刷新菜单后进行绑定即可)</li>
</ul>
<h4><a name="_1491">;</a> 后端代码(逻辑删除)</h4>
<blockquote>
<p>配置逻辑删除</p>
</blockquote>
<p>gulimall-product中的productEntity配置mybatisplus的逻辑删除注解:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/574c19e3bf8e1bbb63f941a95a73bbe6.png" /></p>
<pre><code class="language-java">
@TableLogic(value = "1", delval = "0")
private Integer showStatus;
</code></pre>
<p>配置好了之后,我们去使用mp中baseMapper的查询与删除,默认会走的是逻辑删除及查询对应status=0的所有记录!</p>
<blockquote>
<p>删除代码逻辑</p>
</blockquote>
<p><code>CategoryController.java</code>:</p>
<pre><code class="language-java">@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping("/delete")
public R delete(@RequestBody Long[] catIds){
categoryService.removeMenuByIds(Arrays.asList(catIds));
return R.ok();
}
}
</code></pre>
<p><code>CategoryServiceImpl.java</code>:当前仅仅直接实现了批量删除,对于引用的代码并没有进行编写</p>
<pre><code class="language-java">@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
@Override
public void removeMenuByIds(List<Long> asList) {
baseMapper.deleteBatchIds(asList);
}
}
</code></pre>
<h4>前端代码</h4>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/d8f5d97479a91d4db07d9769fa40f898.png" /></p>
<pre><code class="language-vue">
{{ node.label }}
Append
Delete
// 方法集合
methods: {
//删除单个分类
remove(node, data) {
console.log(node, data);
this.$confirm(
此操作将刪除分类[${node.label}], 是否继续?, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
//构成id
var ids = [data.catId];
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "删除成功!"
});
console.log("刪除成功!");
//刷新菜單
this.getMenus();
//设置展开的是当前删除节点的父catId
this.expandedKey = [node.parent.data.catId];
});
})
.catch(() => {});
},
}
</code></pre>
<h3>3.1.5、新增单个分类(后端+前端实现)</h3>
<h4>效果展示及思路</h4>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/a050b1de6898a936853895ea326b17f8.gif" /></p>
<p>思路:</p>
<p>1、点击Append,出现弹窗,输入内容。</p>
<p>2、点击确定,发起新增请求,请求结束刷新菜单,显示已经展开的分栏框。</p>
<h4><a name="_1643">;</a> 后端代码</h4>
<p>直接就是之前逆向生成的代码,我们无需进行修改:</p>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/239d295703375ef95d2d4ee8f76a129a.png" /></p>
<pre><code class="language-java">@RestController
@RequestMapping("product/category")
public class CategoryController {
@PostMapping("/save")
public R save(@RequestBody CategoryEntity category){
categoryService.save(category);
return R.ok();
}
}
</code></pre>
<h4>前端代码</h4>
<pre><code class="language-vue">
取 消
确 定
export default {
mehtods: {
//el-tree组件调用的append方法
append(data) {
console.log("---append---,data", data);
//打开窗口
this.dialogVisible = true;
this.category.parentCid = data.catId; //父分类id
this.category.catLevel = data.catLevel * 1 + 1; //分类id
this.category.catId = null; //待服务器自己生成
this.category.name = "";
this.category.icon = "";
this.category.productUnit = "";
this.category.sort = 0;
this.category.showStatus = 1;
console.log("待apennd数据", this.category);
console.log("---append---");
},
//添加三级分类
addCategory() {
console.log("---addCategory---")
console.log(this.category)
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(this.category, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "新增成功!"
});
//关闭窗口
this.dialogVisible = false;
//刷新菜單
this.getMenus();
//设置展开的是当前删除节点的父catId
this.expandedKey = [this.category.parentCid];
});
console.log("---addCategory---")
},
}
}
</code></pre>
<h3>3.1.6、编辑单个分类(后端+前端实现)</h3>
<h4>效果展示及思路</h4>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/16b7f42a35801f719055094672a2dd81.gif" /></p>
<p>编辑分类思路:</p>
<p>1、点击编辑回显分类信息,这个信息一定要从 <strong>服务器中进行查询</strong>,否则可能会出现多个管理员去操作时显示的数据还是之前树型结构的。</p>
<p>2、对于真正的编辑请求中 <strong>携带的数据仅仅只是要进行修改的属性</strong>,其他不修改的一律不用携带。</p>
<h4><a name="_1746">;</a> 后端代码</h4>
<p>①根据catId查询分类信息接口</p>
<pre><code class="language-java">@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@RequestMapping("/info/{catId}")
public R info(@PathVariable("catId") Long catId){
CategoryEntity category = categoryService.getById(catId);
return R.ok().put("category", category);
}
}
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/cd603b421c305b1d663ba2a6b32acb7e.png" /></p>
<p>②编辑分类接口</p>
<pre><code class="language-java">@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping("/update")
public R update(@RequestBody CategoryEntity category){
categoryService.updateById(category);
return R.ok();
}
}
</code></pre>
<p><img alt="谷粒商城-基础篇(详细流程梳理+代码)" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/3523d023e2dccc58aad1225eb2dee69a.png" /></p>
<h4>前端代码</h4>
<p>前端部分编辑的分类我们是复用之前的新增分类弹窗,对于新增、编辑操作区分我们通过一个单独的属性 <code>dialogType</code>来表示,对于窗口的标题名称使用title属性来表示:</p>
<pre><code class="language-vue">
取 消
确 定
</code></pre>
<p>而对于真正提交确定按钮,我们则可以根据对应的dialogType来进行表示:</p>
<pre><code class="language-js">
edit(data) {
console.log("---edit---");
this.dialogType = "edit";
this.title = "编辑标签";
this.$http({
url: this.$http.adornUrl(
/product/category/info/${data.catId}`),
method: "get"
}).then(({ data }) => {
this.dialogVisible = true;
console.log(data.category);
this.category = data.category;
console.log("---edit---");
});
},
editCategory() {
console.log("---提交编辑表单---");
let data = {
catId: this.category.catId,
name: this.category.name,
icon: this.category.icon,
productUnit: this.category.productUnit
};
console.log(data);
this.$http({
url: this.$http.adornUrl("/product/category/update"),
method: "post",
data: this.$http.adornData(data, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "编辑成功!"
});
this.dialogVisible = false;
this.getMenus();
this.expandedKey = [this.category.parentCid];
console.log("---提交编辑表单---");
});
},
submitData() {
console.log("---提交数据---");
if (this.dialogType == "add") {
this.addCategory();
} else if (this.dialogType == "edit") {
this.editCategory();
}
console.log("---提交数据---");
},
3.1.7、拖拽分类并实现批量保存(后端+代码实现)
效果展示及思路
拖拽展示:
从图中你可以看到只有在开启拖拽选项后,才能够进行拖拽操作,这个小功能实际上对于element ui组件是只需要配置下的,我们只需要控制 draggable属性即可控制是否拖拽!
1、对于是否能够拖到指定的层级位置,是要进行判定的。在当前的系统需求中来说是无法将某个分类拖动到第三级的标签中的。
- 使用
allowDrop(draggingNode, dropNode, type)
方法来判断是否能够进行拖动到目标位置:属性分别是拖动中的节点,目标节点以及拖动类型(type
参数有三种情况:’prev’、’inner’ 和 ‘next’,分别表示放置在目标节点前、插入至目标节点和放置在目标节点后)。 - 判断是否可拖动目标位置逻辑:
–
是否允许拖拽(拖拽过程中会进行调用)before、after、inner
逻辑:
1、计算出当前拖动的节点它的最大深度
2、最大深度 - 当前拖动节点的深度 + 1 + 目标节点的深度 <= 3 符合要求 < code></=>
2、只有当我们在拖拽移动到目标节点按下时,说明是目标想要移动的操作,对于整个操作完成我们应当提前将需要对应需要改动的节点sort、level来进行统一存储起来。
- 这个拖拽完成动作绑定的是el-tree的node-drop方法,包含的属性有:
draggingNode, dropNode, dropType, ev
。 - 拖拽完成的逻辑操作:
–
拖拽结束动作:统一添加到待更新的数组中 before、after、inner
逻辑:
1、获取到目标节点的最新父节点id并收集(目标是为了之后进行刷新展开对应节点)
2、得到父节点之后取得它的所有子节点,去统一收集三个部分内容
第一部分:所有子节点中非移动节点的catId、sort
第二部分:目标节点1个,更新该目标节点的catId、parentId、sort、catLevel
第三部分:目标节点的所有子节点,这些节点无需更新他们的sort,即catId、catLevel(递归处理)
核心在这个过程中需要存储的有:①批量保存后要展开的父节点。②所有需要保存的节点状态。
pCid: [],
updateNodes: [],
批量保存实现:
批量保存点击之后流程:
1、发送待更新数组,进行批量更新请求。(使用到updateNodes)
2、更新完成之后,展开所有移动目标节点的父节点位置。(使用到pCid)
3、重置updateNodes、pCid数组。
后端代码
对于批量更新分类的后端代码比较简单,直接就是把前端传来的进行批量操作即可:
@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping("/update/sort")
public R updateSort(@RequestBody CategoryEntity[] category) {
categoryService.updateBatchById(Arrays.asList(category));
return R.ok();
}
}
前端代码
组件及过程属性:
批量保存
{{ node.label }}
Append
edit
Delete
data() {
return {
maxLevel: 0,
pCid: [],
updateNodes: [],
};
},
拖拽过程中主要有两个触发函数:
allowDrop(draggingNode, dropNode, type) {
console.log(draggingNode, dropNode, type);
this.maxLevel = draggingNode.level;
this.countNodeLevel(draggingNode);
let deep = this.maxLevel - draggingNode.level + 1;
if (type == "inner") {
return deep + dropNode.level 3;
} else {
return deep + dropNode.parent.level 3;
}
},
countNodeLevel(node) {
if (node.childNodes != null && node.childNodes.length > 0) {
for (let i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].level > this.maxLevel) {
this.maxLevel = node.childNodes[i].level;
}
this.countNodeLevel(node.childNodes[i]);
}
}
},
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("拖拽结束", draggingNode, dropNode, dropType, ev);
let pCid = 0;
let siblings = null;
if (dropType == "before" || dropType == "after") {
pCid =
dropNode.parent.data.catId == undefined
? 0
: dropNode.parent.data.catId;
siblings = dropNode.parent.childNodes;
} else {
pCid = dropNode.data.catId;
siblings = dropNode.childNodes;
}
this.pCid.push(pCid);
for (let i = 0; i < siblings.length; i++) {
if (siblings[i].data.catId == draggingNode.data.catId) {
let catLevel = draggingNode.level;
if (siblings[i].level != catLevel) {
catLevel = siblings[i].level;
console.log("第三部分,", siblings[i]);
this.updateChildNodeLevel(siblings[i]);
}
this.updateNodes.push({
catId: siblings[i].data.catId,
parentCid: pCid,
sort: i,
catLevel: catLevel
});
} else {
this.updateNodes.push({
catId: siblings[i].data.catId,
sort: i
});
}
}
console.log("updateNodes", this.updateNodes);
},
updateChildNodeLevel(node) {
console.log("node.childNodes.length, ", node.childNodes.length);
if (node.childNodes.length > 0) {
for (let i = 0; i < node.childNodes.length; i++) {
var cNode = node.childNodes[i].data;
this.updateNodes.push({
catId: cNode.catId,
catLevel: node.childNodes[i].level
});
console.log("递归处理:", node.childNodes[i]);
this.updateChildNodeLevel(node.childNodes[i]);
}
}
},
批量保存就是一个保存函数:
batchSave() {
this.$http({
url: this.$http.adornUrl("/product/category/update/sort"),
method: "post",
data: this.$http.adornData(this.updateNodes, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "分类批量保存成功!"
});
this.getMenus();
this.expandedKey = this.pCid;
this.updateNodes = [];
this.pCid = [];
});
}
3.1.8、批量删除分类(后端+代码实现)
效果展示及思路
效果展示:
批量删除逻辑:
1、获取到选中的所有节点catId,点击批量删除即可发送删除请求。
如何获取到所有选中的节点呢?可借助el-tree中自带的getCheckedNodes()函数,首先需要去进行ref绑定,绑定好之后即可在对应的删除功能函数中进行调用方法函数去获取到所有的选中节点。
- 对于半选的节点我们可以通过给getCheckedNodes()传参来进行获取,看下下面的官方注释:
; 后端代码
同样使用的是之前删除单个分类的接口,在这个接口中暂时还没有去检查对应的菜单是否被引用:
前端代码
这里获取选中的所有节点的catId也是比较简单,由于el-tree本身给我们提供了获取选中节点的方法,那么我们这边只需要进行去组装所有列表中的catId即可:
批量删除
batchDelete() {
let ids = []
let checkNodes = this.$refs.menuTree.getCheckedNodes(false, false);
console.log("被选中的节点:", checkNodes);
for (let i = 0; i < checkNodes.length; i++) {
ids.push(checkNodes[i].catId);
}
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "批量删除成功!"
});
this.getMenus();
});
}
3.2、品牌管理API
3.2.1、通过逆向工程快速构建品牌管理页面
首先我们来进行手动添加菜单:
找到对应逆向生成的品牌brand表的增删改页面代码:
将其添加到我们的vue项目的views目录下:
此时我们进入到管理界面就可以看到基于数据库pms_brand表中属性的基本增删改查代码就已经完成了。
为什么对于新增、修改上面的按钮没有呢?主要是因为renren-fast有对应的权限校验,我们将对应的校验代码注释了即可(暂时)。
找到对应的isAuth函数,暂时将其中的代码注释,并且直接返回true即可。
OK,至此新增、批量删除的按钮就已经出现了:
; 3.2.2、品牌状态显示优化及与后端交互(后端+前端实现)
效果展示及思路
对于管理的页面状态显示:
新增按钮点击展示的显示状态部分:
将原本显示的状态值1或0更改为这种开关形式,对于上面的两个显示状态用途如下:
1、管理页面显示状态:点击开关时就会向后台去更新状态。
2、新增页面的:仅仅只是为了在新增时更加简便的操作。
; 后端代码
@RestController
@RequestMapping("product/brand")
public class BrandController {
@Autowired
private BrandService brandService;
@RequestMapping("/update/status")
public R updateStatus(@RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
}
对应的api接口如下,该接口会通过网关来进行转发到商品服务(在此之前是有在网关来进行配置的):
前端代码
管理系统中的显示部分:
对于这种在el-table中来进行设置switch的我们需要使用到插槽,设置插槽之后在其中编写el-switch组件即可,并且对应的想要获取到对应的showStatus,可以借助scope来进行获取:
当我们来进行点击开关按钮的时候,实际上就需要来进行发送请求去更新某个品牌的状态了,所以这个更改状态机制可以借助el-switch给我们提供的change方法,我们来自己编写一个更新状态的函数,在其中去发送请求即可:
updateBrandStatus(data) {
console.log("最新值为:", data);
let {brandId, showStatus} = data
this.$http({
url: this.$http.adornUrl("/product/brand/update/status"),
method: "post",
data: this.$http.adornData({brandId, showStatus}, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "状态更新成功!"
});
});
},
新增按钮页面显示部分:
对于el-form表单中若是想要修改对应的表单内容就无需使用插槽,直接添加对应的switch组件即可,其中的状态值获取使用对应的dataForm.showStatus即可:
3.2.3、集成OSS服务并创建上传组件(获取服务端签名方式上传,品牌新增、编辑)
前言:认识文件存储服务与服务端签名
文件存储服务如下:
阿里云开通OSS存储服务可见我的博客:阿里云开通OSS存储服务详细流程
对于图片上传方式有两种:OSS两种上传方式案例demo可见我的博客:SpringBoot集成阿里云OSS存储服务(普通上传、服务端签名上传)
第一种:普通上传方式
第二种:服务端签名后直传
思路:利用阿里云提供的一个防伪的签名,客户端从服务器获取到之后拿着防伪签名+图片资源来进行直接上传oss,oss会进行验证防伪签名。
过程问题:web前端给OSS直接发送请求会出现跨域请求问题怎么解决?
解决方案:直接在oss上来进行设置允许跨域。
项目分析:在谷粒商城中我们构建第一个第三方服务的模块,将OSS以及一些其他的第三方模块来进行引入,之后若是有服务需要进行第三方服务的构建直接引入依赖即可!
; 3.2.3.1、创建gulimall-third-party模块
直接finish即可创建完成:
3.2.3.2、nacos创建命名空间与配置文件
创建 third-party
的命名空间:
创建配置third-party命名空间中的配置文件 oss.yml
:
spring:
cloud:
alicloud:
access-key:
secret-key:
oss:
endpoint:
bucketname:
3.2.3.3、搭建OSS服务签名服务(最佳实践)
阿里云官网bucket开启跨域
文档地址:最佳实践-web端上传数据至OSS-服务器端直传并设置上传回调
创建Bucket、配置用户账号以及允许跨域操作如下:
若是我们仅仅用来自己项目测试的话可以按照下面的来进行创建:
记住要为对应的bucket设置对应的用户权限:对于创建用户操作见我上面的文章链接跟着走一遍就行!
配置跨域请求:
; 后端实现代码
首先是配置 pom.xml
:引入的有公共模块、openfeign、springcloud-alibaba-oss服务
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.8.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Greenwich.SR3spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>com.atguigu.gulimallgroupId>
<artifactId>gulimall-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alicloud-ossartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
application.yaml
:配置端口号
server:
port: 30000
bootstrap.properties
:服务名、注册中心配置中心地址以及额外从配置中心读取的配置内容
spring.application.name=gulimall-third-party
服务注册
spring.cloud.nacos.discovery.server-addr=localhost:8848
配置中心
spring.cloud.nacos.config.server-addr=localhost:8848
spring.cloud.nacos.config.namespace=0dba868a-457a-4da6-8de2-9811b8585f7b
额外配置选项
spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true
GulimallThirdPartyApplication.java
:启动器上去添加服务注册发现以及排除数据源自动配置类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
OssController.java
:接着就是我们的获取OSS签名服务
package com.atguigu.gulimall.thirdparty.controller;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.atguigu.common.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
public class OssController {
@Autowired
private OSS ossClient;
@Value("${spring.cloud.alicloud.oss.bucketname}")
private String bucketName;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@RequestMapping("/oss/policy")
public R policy() {
String host = "https://" + bucketName + "." + endpoint;
String dirName = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = dirName;
Map<String, String> respMap = null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
System.out.println(e.getMessage());
}
return R.ok().put("data", respMap);
}
}
启动该服务后测试下是否能够获取到签名:
前端上传组件引入及使用(品牌编辑)
引入upload组件以及修改上传地址
谷粒商城已经给我们封装好了上传组件:
- 组件内部流程:点击上传之后,①首先会到服务器端获取到签名。②拿到签名之后再从web端去直接向阿里云的OSS服务发起上传请求,最终由组件来自己拼接访问地址传给我们自己绑定的属性中。
链接:https://pan.baidu.com/s/1GLu4i1m27N6ioTE-RFP6fg
提取码:pecf
将其放置到Components目录下:
在品牌编辑中我们仅仅只需要一个单文件上传组件,所以我们需要修改单文件上传组件的action:
修改的目标地址如下面红框中的地址:
此时准备操作已经完成!
集成上传组件到编辑品牌页
目标需求:
实践:
找到/views/product/brand-add-or-update.vue文件,来引入单文件上传组件,并进行编辑:
在script标签中引入我们的单文件上传组件:
import SingleUpload from "@/components/upload/singleUpload"
export default {
components: {SingleUpload},
在el-form-item标签中将原本的input换为我们的upload组件:
到此为止,我们就已经完成了集成上传功能!
测试一下:
点击修改,接着我们来点击上传,若是出现效果如下表示上传成功:
Original: https://blog.csdn.net/cl939974883/article/details/127761005
Author: 长路 ㅤ
Title: 谷粒商城-基础篇(详细流程梳理+代码)
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/658522/
转载文章受原作者版权保护。转载请注明原作者出处!