1 项目简介
1.2 项目架构图
1.2.1 项目微服务架构图
1.2.2 微服务划分图
2 分布式基础概念
3 Linux系统环境搭建
查看网络IP和网关
linux网络环境配置
补充P123(修改linux网络设置&开启root密码访问)
设置主机名和hosts映射
主机名解析过程分析(Hosts、DNS)
3.1 安装linux虚拟机
3.2 安装docker
官网安装地址:Install Docker Engine on CentOS | Docker Docs
(1)卸载系统中的docker
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
(2)安装docker依赖包
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
(3)设置docker仓库地址(告诉linux去哪里下载docker)
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
可能会报错:
Could not fetch/save url https://download.docker.com/linux/centos/docker-ce.repo to file /etc/yum.repos.d/docker-ce.repo: [Errno 14] curl#7 - “Failed to connect to 2a03:2880:f12a:83:face:b00c:0:25de: Network is unreachable”
解决办法:服务器下载docker的镜像仓库失败,切换成阿里云的镜像下载
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
(4)安装docker
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
(5)启动docker
sudo systemctl start docker
(6)设置docker开机自动启动
systemctl enable docker
(7)配置docker阿里云镜像加速
阿里云官网:阿里云权益中心_助力学生、开发者、企业用云快速上云-阿里云
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://mysouset.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
如果后续docker pull拉取镜像失败,添加以下库:
vi /etc/docker/daemon.json
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.jianmuhub.com",
"https://huecker.io",
"https://dockerhub.timeweb.cloud",
"https://dockerhub1.beget.com",
"https://noohub.ru"
]
}
配置完镜像源后需要重启
# 重新加载配置文件
sudo systemctl daemon-reload
# 重新启动docker服务
sudo systemctl restart docker
3.3 docker安装mysql
(1)下载镜像文件
docker pull mysql:5.7
(2)创建实例并启动
docker run -p 3306:3306 --privileged=true --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql/config.d \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
docker容器文件挂载与端口映射
访问容器内部,进入Linux的/bin/bash控制台:
docker exec -it mysql /bin/bash
进入docker容器显示bash-4.2# 而不是root@容器id 的方式,这是由于docker容器/etc/skel目录下缺失2个配置文件,从默认配置中拷贝过来就可以解决了:
cp /etc/skel/.bashrc /root/
cp /etc/skel/.bash_profile /root/
mysql配置
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
修改配置后重启容器
docker restart mysql
3.4 docker安装redis
(1)下载镜像文件
docker pull redis
(2)创建实例并启动
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
(3)检查redis是否启动成功
docker exec -it redis redis-cli
(4)redis开启持久化
vi /mydata/redis/conf/redis.conf
让redis启用AOF的持久化方式
appendonly yes
3.5 开发环境统一
3.5.1 Maven
3.5.2 IDEA&VS Code
VS Code插件:
Auto Close Ta;Auto Rename Tag;ESLint;HTML CSS Support;HTML Snippets;JavaScript(ES6);Live Server(实时服务器);open in browser(在浏览器打开页面);Vetur(开发Vue项目常用的工具);Vue 2 Snippet,Vue 3 Snippets(Vue语法提示)
3.5.3 安装配置git
1、下载git:https://git-scm.com
2、配置git,进入git bash
# 配置用户名
git config --global user.name "username"
# 配置游戏
git config --global user.email "username@email.com"
保存于 ~/.gitconfig文件中
3、配置ssh免密登录
3.5.4 逆向工程使用
1、导入项目逆向工程
2、下载人人开源后台管理系统脚手架工程
(1)导入工程,创建数据库
(2)修改工程shiro依赖为SpringSecurity
(3)删除部分暂时不需要的业务
3、下载人人开源后台管理系统vue端脚手架工程
(1)vscode导入前端项目
(2)前后端联调测试基本功能
3.6 创建项目微服务
商品服务、仓储服务、订单服务、优惠券服务、用户服务
共同:
1)web、openfeign
2)每一个服务,包名 com.atguigu.gulimall.xxx(product/order/ware/coupon/member)
3)模块名:gulimall-xxx
新建gitee仓库
创建微服务模块
商品服务
将gulimall设为总项目,聚合其他项目
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall</name>
<description>聚合服务</description>
<packaging>pom</packaging>
<modules>
<module>gulimall-coupon</module>
<module>gulimall-member</module>
<module>gulimall-order</module>
<module>gulimall-product</module>
<module>gulimall-ware</module>
</modules>
</project>
修改总项目的.gitignore模板
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
#任意路径下忽略mvnw文件
**/mvnw
#任意路径下忽略mvnw.cmd文件
**/mvnw.cmd
#任意路径下忽略.mvn目录
**/.mvn
#任意路径下忽略target目录
**/target/
#忽略同级的.idea
.idea
#子模块的.gitignore
**/.gitignore
commit提交时,去掉:分析所有代码;检查哪些没有做两个勾选
数据库初始化
powerdesigner安装(不要汉化,亲测汉化后缺少功能)
PowerDesigner安装详细教程_powerdesigner安装步骤-CSDN博客
设docker启动后容器自动启动
docker update mysql --restart=always
docker update redis --restart=always
4 前端开发基础知识
4.1 Node.js
npm install下载所需的依赖,package.json描述每一个依赖所需要的版本
在使用原来的淘宝镜像地址https://registry.npm.taobao.org/时,浏览器会自动跳转https://registry.npmmirror.com/,可能会导致我们使用npm下载的时候请求下载的很慢
设置最新淘宝镜像
npm config set registry https://registry.npmmirror.com/
查看是否已经配置成功
npm config get registry
安装pnpm
npm install --global pnpm
配置PNPM镜像源(默认已经为新版淘宝镜像)
pnpm config set registry https://registry.npmmirror.com
解决node-sass问题,支持高版本node以及pnpm(gitee评论区大神)
pnpm install --ignore-scripts
pnpm remove node-sass sass-loader
pnpm install --save sass-loader@7 sass babel-runtime qs vue-hot-reload-api svg-baker-runtime
5 分布式组件
微服务-注册中心、配置中心、网关
Spring Cloud Alibaba:
https://github.com/alibaba/spring-cloud-alibaba
在common项目中引入如下。进行统一管理
<dependencyManagement>
<dependencies>
<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>
5.1 Nacos
5.1.1 Nacos作为注册中心
1)下载nacos-server
https://github.com/alibaba/nacos/releases
2)启动nacos-server
双击bin中的startup.cmd文件;访问https://localhost:8848/nacos/;使用默认的nacos/nacos进行登录
3)将微服务注册到nacos中
1 首先,修改pom.xml文件,引入Nacos Discovery Starter
<!--服务注册/发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2 在应用的 /src/main/resources/application.properties 配置文件中配置 Nacos Server地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
3 使用 @EnableDiscoveryClient 注解开启服务注册与发现功能
package com.atguigu.gulimall.coupon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCouponApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}
}
4 启动应用,观察nacos服务列表是否已经注册上服务
注意:每一个应用都应该有名字,这样才能注册上去。
在应用的 /src/main/resources/application.properties 配置文件中给微服务起注册中心的名字
spring.application.name=gulimall-coupon
5.1.2 Nacos作为配置中心
1、如何使用Nacos作为配置中心统一管理配置
1)首先,修改pom.xml文件,引入Nacos Config Starter
<!--配置中心来做配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2)在应用的 /src/main/resources/bootstrap.properties 配置文件中配置 Nacos Config元数据
#设置服务名
spring.application.name=gulimall-coupon
#设置配置中心地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
3)需要给配置中心添加一个叫数据集(Data Id)gulimall-coupon.properties
默认规则:应用名.properties
4)给应用名.properties添加任何配置
5)动态获取配置
@RefreshScope:动态获取并刷新配置
@Value("${配置项的名}"):获取到配置
如果配置中心和当前的配置文件中都配置了相同的项,优先使用配置中心的配置
2、细节
1)命名空间:配置隔离;
默认:public(保留空间);默认新增的所有配置都在public空间。
1、开发,测试,生产:利用命名空间来做环境隔离。
注意:在bootstrap.properties;配置上,需要使用哪个命名空间下的配置,
spring.cloud.nacos.config.namespace=9de62e44-cd2a-4a82-bf5c-95878bd5e871
2、每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
2)、配置集:所有的配置的集合
3)、配置集ID:类似文件名。
Data ID:类似文件名
4)、配置分组:
默认所有的配置集都属于:DEFAULT_GROUP;
1111,618,1212
项目中的使用:每个微服务创建自己的命名空间,使用配置分组区分环境,dev,test,prod
3、同时加载多个配置集
1)微服务任何配置信息,任何配置文件都可以放在配置中心中
2)只需要在bootstrap.properties说明加载配置中心中哪些配置文件即可
3)@Value,@ConfigurationProperties。。。
以前SpringBoot任何方法从配置文件中获取值,都能使用。
配置中心有的优先使用配置中心中的
5.1.3 Gateway网关
客户端直接请求服务
客户端请求API网关,由网关代转给各个服务
工作流程
5.2 SpringCloud Feign 声明式远程调用
6 前端基础
前端技术栈类比
6.1 VSCode
6.2 ES6
6.2.1 简介
6.2.2 什么是ECMAScript
6.2.3 ES6新特性
6.2.3.1 let声明变量
6.2.3.2 const声明变量(只读变量/常量)
6.2.3.3 解构表达式
1)数组解构
2)对象解构
6.2.3.4 字符串扩展
1)几个新的API
2)字符串模板
6.2.3.5 函数优化
1)函数参数默认值
2)不定参数
3)箭头函数
ES6中定义函数的简写方式
一个参数时:
多个参数:
4)实战:箭头函数结合解构表达式
6.2.3.6 对象优化
1)新增的API
2)声明对象简写
3)对象的函数属性简写
4)对象拓展运算符
6.2.3.7 map和reduce
6.2.3.8 Promise
1)Promise语法
2)处理异步结果
3)Promise改造以前嵌套方式
4)优化处理
6.2.3.9 模块化
1)什么是模块化
2)export
3)import
6.3 Node.js
npm install下载所需的依赖,package.json描述每一个依赖所需要的版本
在使用原来的淘宝镜像地址https://registry.npm.taobao.org/时,浏览器会自动跳转https://registry.npmmirror.com/,可能会导致我们使用npm下载的时候请求下载的很慢
设置最新淘宝镜像
npm config set registry https://registry.npmmirror.com/
查看是否已经配置成功
npm config get registry
安装pnpm
npm install --global pnpm
配置PNPM镜像源(默认已经为新版淘宝镜像)
pnpm config set registry https://registry.npmmirror.com
解决node-sass问题,支持高版本node以及pnpm(gitee评论区大神)
pnpm install --ignore-scripts
pnpm remove node-sass sass-loader
pnpm install --save sass-loader@7 sass babel-runtime qs vue-hot-reload-api svg-baker-runtime
6.4 Vue
浏览器安装Vue-Devtools(P37)
6.4.1 MVVM思想
6.4.2 Vue简介
6.4.3 入门案例
6.4.4 概念
6.4.5 指令
6.4.5.1 插值表达式
1)花括号
2)插值闪烁
3)v-text和v-html
6.4.5.2 v-bind(单向绑定)简写为 :
6.4.5.3 v-model(双向绑定)
6.4.5.4 v-on(绑定事件)简写为 @
1、基本用法
2、事件修饰符
3、按键修饰符
4、组合按钮
6.4.5.5 v-for
6.4.5.6 v-if和v-show
6.4.5.7 v-else和v-else-if
6.4.6 计算属性和侦听器
老写法:
ES6语法:
计算属性和侦听器
过滤器
6.4.7 组件化
1、全局组件
2、组件的复用
3、局部组件
6.4.8 生命周期钩子函数
1、生命周期
2、钩子函数
6.4.9 vue模块化开发
防止安装的时候在cmd窗口鼠标点击卡住,去掉快速编辑模式勾选
1、npm install webpack -g
全局安装webpack
2、npm install -g @vue/cli-init
全局安装vue脚手架
3、初始化vue项目
vue init webpack appname:vue脚手架使用webpack模板初始化一个appname项目
4、启动vue项目
项目的package.json中有scripts,代表我们能运行的命令
npm start = npm run dev:启动项目
npm run build:将项目打包
5、模块化开发
1)项目结构
2)Vue单文件组件
3)vscode添加用户代码片段(快速生成vue模板)
4)导入element-ui快速开发
6.5 Babel
6.6 Webpack
7 商品服务
7.1 基础概念
7.1.1 三级分类
7.1.2 SPU与SKU
P70
7.1.3 基本属性【规格参数】与销售属性
7.2 接口编写
7.2.1 HTTP请求模板
7.2.2 JSR303 数据局校验
7.2.3 全局异常处理
7.2.4 接口文档地址
https://easydoc.net/#/s/78237135
7.2.5 Obect划分★
7.2.5.1 PO(persistant object) 持久对象
PO就是对应数据库中某个表的一条记录,多个记录可以用PO的集合。PO中应该不包含任何对数据库的操作
7.2.5.2 DO(Domain Object)领域对象
就是从现实世界中抽象出来的有形或无形的业务实体
7.2.5.3 TO(Transfer Object),数据传输对象
不同的应用程序之间传输的对象
7.2.5.4 DTO(Data Transfer Object) 数据传输对象
这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层与服务层之间的数据传输对象
7.2.5.5 VO(value obect)值对象
通常用于业务层之前的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要。用new关键字创建,由GC回收的。
也可以叫
View object:视图对象;更形象
接收页面传递来的数据,封装对象
将业务处理完成的对象,封装成页面要用的数据
7.2.5.6 BO(business object) 业务对象
7.2.5.7 POJO(plain ordinary java object) 简单无规则java对象
7.2.5.8 DAO(data access object) 数据访问对象
7.3 网关统一配置跨域
P47
跨域
跨域流程
官方文档对跨域解释说明:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
7.3.1 解决跨域
开发阶段先采用方式二:
8 商品服务-品牌管理
8.1 阿里云对象存储oss
P62阿里云文件存储
8.2 JSR303数据校验
P65前端表单校验
P66后端校验
P68JSR303分组校验
P69JSR303自定义校验注解
8.3 错误码和错误信息定义类
P67统一异常处理(AOP)
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5位数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:10001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10:通用
* 001:参数格式校验
* 11:商品
* 12:订单
* 13:购物车
* 14:物流
*
*
*/
9 商品服务-属性分组
P71父子组件交互
10 商品服务-平台属性
P76 vo
11 商品服务-新增商品
11.1 项目笔记:
远程服务调用
P9,11'
gulimall-product中的CouponFeignService.saveSpuBounds(spuBoundT) 远程调用了gulimall-coupon服务,传递了一个对象,不是基本类型的数据。
SpringCloud做的第一步:
1)通过@RequestBody将这个对象转为json
2)在注册中心找到gulimall-coupon服务,给/coupon/spubounds/save发送请求,将上一步转的json放在请求体位置,发送请求;
gulimall-product服务:
package com.atguigu.gulimall.product.feign;
import com.atguigu.common.to.SpuBoundTo;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
/**
* 1、CouponFeignService.saveSpuBounds(spuBoundTo);
* 1)、@RequestBody将这个对象转为json。
* 2)、找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。
* 将上一步转的json放在请求体位置,发送请求;
* 3)、对方服务收到请求。请求体里有json数据。
* (@RequestBody SpuBoundsEntity spuBounds);将请求体的json转为SpuBoundsEntity;
* 只要json数据模型是兼容的。双方服务无需使用同一个to
* @param spuBoundTo
* @return
*/
@PostMapping("/coupon/spubounds/save")
R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
}
gulimall-coupon服务:
package com.atguigu.gulimall.coupon.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.atguigu.gulimall.coupon.entity.SpuBoundsEntity;
import com.atguigu.gulimall.coupon.service.SpuBoundsService;
import com.atguigu.common.utils.R;
/**
* 商品spu积分设置
*/
@RestController
@RequestMapping("coupon/spubounds")
public class SpuBoundsController {
@Autowired
private SpuBoundsService spuBoundsService;
/**
* 保存
*/
@PostMapping("/save")
//@RequiresPermissions("coupon:spubounds:save")
public R save(@RequestBody SpuBoundsEntity spuBounds){
spuBoundsService.save(spuBounds);
return R.ok();
}
}
11.2 设置服务内存占用与批量重启
设置批量重启:
设置服务内存占用:
12 商品服务-商品管理
12.1 SPU检索
SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
12.2 SKU检索
最小存货单位(SKU),全称为Stock Keeping Unit,即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。2023年已经被引申为产品统一编号的简称,每种产品均对应有SKU号。单品:对一种商品而言,当其品牌、型号、配置、等级、花色、包装容量、单位、生产日期、保质期、用途、价格、产地等属性中任一属性与其他商品存在不同时,可称为一个单品。
12.3 SPU规格维护P100
菜单:商品系统-商品维护-spu管理
显示400的,评论区答案
{ path: '/product-attrupdate', component: _import('modules/product/attrupdate'), name: 'attr-update', 编辑meta: { title: '规格维护', isTab: true } }
功能描述:第一次添加商品时,指定了一些属性。点击规格可以进行修改:①首先进行商品规格信息回显;②修改商品规格
①查询商品规格属性
接口文档
商品系统:22、获取spu规格
数据库表
gulimall_pms.pms_product_attr_value(spu属性值)
Controller
package com.atguigu.gulimall.product.controller;
/**
* 商品属性
*
* @author daijinyu
* @email daijinyu@gmail.com
* @date 2024-09-09 01:55:03
*/
@RestController
@RequestMapping("product/attr")
public class AttrController {
@Autowired
private AttrService attrService;
@Autowired
ProductAttrValueService productAttrValueService;
/**
* 22、获取spu规格 /product/attr/base/listforspu/{spuId}
* @param spuId
* @return
*/
@GetMapping("/base/listforspu/{spuId}")
public R baseAttrlistforspu(@PathVariable("spuId") Long spuId){
List<ProductAttrValueEntity> entities = productAttrValueService.baseAttrlistforspu(spuId);
return R.ok().put("data",entities);
}
}
ServiceImpl
package com.atguigu.gulimall.product.service.impl;
@Service("productAttrValueService")
public class ProductAttrValueServiceImpl extends ServiceImpl<ProductAttrValueDao, ProductAttrValueEntity> implements ProductAttrValueService {
/**
* 22、获取spu规格 /product/attr/base/listforspu/{spuId}
* @param spuId
* @return
*/
@Override
public List<ProductAttrValueEntity> baseAttrlistforspu(Long spuId) {
List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
return entities;
}
}
②修改商品规格
接口文档
商品系统:23、修改商品规格
Controller
package com.atguigu.gulimall.product.controller;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.atguigu.gulimall.product.entity.ProductAttrValueEntity;
import com.atguigu.gulimall.product.service.ProductAttrValueService;
import com.atguigu.gulimall.product.vo.AttrGroupRelationVo;
import com.atguigu.gulimall.product.vo.AttrRespVo;
import com.atguigu.gulimall.product.vo.AttrVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.atguigu.gulimall.product.entity.AttrEntity;
import com.atguigu.gulimall.product.service.AttrService;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.R;
/**
* 商品属性
*
* @author daijinyu
* @email daijinyu@gmail.com
* @date 2024-09-09 01:55:03
*/
@RestController
@RequestMapping("product/attr")
public class AttrController {
@Autowired
private AttrService attrService;
@Autowired
ProductAttrValueService productAttrValueService;
/**
* 23、修改商品规格 /product/attr/update/{spuId}
* @param spuId
* @param entities
* @return
*/
@PostMapping("/update/{spuId}")
public R updateSpuAttr(@PathVariable("spuId") Long spuId,
@RequestBody List<ProductAttrValueEntity> entities){
productAttrValueService.updateSpuAttr(spuId,entities);
return R.ok();
}
}
ServiceImpl
由于更新存在这样的问题:以前没录入的数据新录入了,以前录入的数据现在删除了。所以直接先将旧数据删除,再插入新数据
1、删除这个spuId之前对应的所有属性;
2、更新(这里其实就是新插入);
package com.atguigu.gulimall.product.service.impl;
@Service("productAttrValueService")
public class ProductAttrValueServiceImpl extends ServiceImpl<ProductAttrValueDao, ProductAttrValueEntity> implements ProductAttrValueService {
/**
* 23、修改商品规格 /product/attr/update/{spuId}
* @param spuId
* @param entities
*/
@Transactional
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
//1、删除这个spuId之前对应的所有属性
this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id",spuId));
List<ProductAttrValueEntity> collect = entities.stream().map(item -> {
item.setSpuId(spuId);
return item;
}).collect(Collectors.toList());
this.saveBatch(collect);
}
}
13 仓储服务-仓库管理
13.1 合并采购需求P97
菜单:库存系统-采购单维护
采购单状态的枚举类写在gulimall-common中
package com.atguigu.common.constant;
public class WareConstant {
//采购单-状态
public enum PurchaseStatusEnum{
CREATED(0,"新建"),ASSIGNED(1,"已分配"),
RECEIVE(2,"已领取"),FINISH(3,"已完成"),
HASERROR(4,"有异常");
private int code;
private String msg;
PurchaseStatusEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
//采购需求-状态
public enum PurchaseDetailStatusEnum{
CREATED(0,"新建"),ASSIGNED(1,"已分配"),
BUYING(2,"正在采购"),FINISH(3,"已完成"),
HASERROR(4,"采购失败");
private int code;
private String msg;
PurchaseDetailStatusEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
}
①点击库存系统-采购单维护-采购需求的合并整单,查询新建与已分配的采购单
接口文档
库存系统:05、查询未领取的采购单
②点击库存系统-采购单维护-采购需求的合并整单的确定,将库存系统-采购单维护-采购需求合并到库存系统-采购单维护-采购单
合并整单时,可以不选择采购单,则自动新建一条采购单;如果选择采购单,则将当前采购需求合并到对应的采购单。
采购需求(wms_purchase_detail)合并到采购单(wms_purchase)时:修改wms_purchase_detail的purchase_id和status。purchase_id修改为新增的采购单id或者选择的采购单id;status修改为已分配
接口文档
库存系统:04、合并采购需求
Controller
13.2 领取采购单P98
采购人员领取采购单,完成采购,将商品入库。
采购单一经领取,采购单状态变为已领取。同时,已领取的采购单,采购需求不能再被分配,且采购需求状态变为正在采购。
接口文档
库存系统:06、领取采购单
①领取新建或者已分配状态的采购单
根据传参过来的采购单,先查询出所有采购单;通过stream流的filter过滤方法过滤出新建或者已分配状态的采购单;
②改变采购单的状态
将过滤出的采购单状态修改为已领取;
③改变采购项(采购需求)的状态
通过采购单id查找出所有采购项(采购需求);修改采购项的状态为正在采购;
/**
* 领取采购单
* @param ids 采购单id
*/
@Override
public void received(List<Long> ids) {
//1、确认当前采购单是新建或者已分配状态
List<PurchaseEntity> collect = ids.stream().map(id -> {
PurchaseEntity byId = this.getById(id);
return byId;
}).filter(item -> {
if (item.getStatus() == WareConstant.PurchaseStatusEnum.CREATED.getCode() ||
item.getStatus() == WareConstant.PurchaseStatusEnum.ASSIGNED.getCode()) {
return true;
}
return false;
}).map(item->{
item.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());
item.setUpdateTime(new Date());
return item;
}).collect(Collectors.toList());
//2、改变采购单的状态
this.updateBatchById(collect);
//3、改变采购项的状态
collect.forEach((item)->{
List<PurchaseDetailEntity> entities = detailService.listDetailByPurchaseId(item.getId());
List<PurchaseDetailEntity> detailEntities = entities.stream().map(entity -> {
PurchaseDetailEntity entity1 = new PurchaseDetailEntity();
entity1.setId(entity.getId());
entity1.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
return entity1;
}).collect(Collectors.toList());
detailService.updateBatchById(detailEntities);
});
}
13.3 完成采购 P99
采购人员通过手机app领取采购单,领取完成以后点击完成,完成采购单,即:采购需求中的商品id和对应的采购数量入库保存
完成采购:传入采购单id,采购单关联的采购项(采购需求),采购人员在app上进行勾选,改变采购项的状态。采购失败(没勾选的,没完成的)需要填写采购失败原因
待完善:
采购项表gulimall_wms.wms_purchase_detail没有失败原因reason字段
接口文档
库存系统:07、完成采购
完成采购单Vo
package com.atguigu.gulimall.ware.vo;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
//完成采购单Vo
@Data
public class PurchaseDoneVo {
@NotNull
private Long id;//采购单id
private List<PurchaseItemDoneVo> items;
}
完成采购项Vo
package com.atguigu.gulimall.ware.vo;
import lombok.Data;
//完成采购项Vo
@Data
public class PurchaseItemDoneVo {
//{itemId:1,status:4,reason:""}
private Long itemId;
private Integer status;
private String reason;
}
Controller
@Autowired
private PurchaseService purchaseService;
/**
* 07、完成采购 /ware/purchase/done
* @param doneVo
* @return
*/
@PostMapping("/done")
public R finish(@RequestBody PurchaseDoneVo doneVo){
purchaseService.done(doneVo);
return R.ok();
}
ServiceImpl
package com.atguigu.gulimall.ware.service.impl;
@Service("purchaseService")
public class PurchaseServiceImpl extends ServiceImpl<PurchaseDao, PurchaseEntity> implements PurchaseService {
@Autowired
PurchaseDetailService detailService;
@Autowired
WareSkuService wareSkuService;
/**
* 07、完成采购 /ware/purchase/done
* @param doneVo
*/
@Transactional
@Override
public void done(PurchaseDoneVo doneVo) {
Long id = doneVo.getId();
//2、改变采购项的状态
Boolean flag = true;
List<PurchaseItemDoneVo> items = doneVo.getItems();
List<PurchaseDetailEntity> updates = new ArrayList<>();
for (PurchaseItemDoneVo item : items) {
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
if(item.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()){
flag = false;
detailEntity.setStatus(item.getStatus());
}else{
detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.FINISH.getCode());
3、将成功采购的进行入库
PurchaseDetailEntity entity = detailService.getById(item.getItemId());
wareSkuService.addStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum());
}
detailEntity.setId(item.getItemId());
updates.add(detailEntity);
}
detailService.updateBatchById(updates);
//1、改变采购单状态
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(id);
purchaseEntity.setStatus(flag?WareConstant.PurchaseStatusEnum.FINISH.getCode():WareConstant.PurchaseStatusEnum.HASERROR.getCode());
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
}
}
WareSkuServiceImpl.addStock()
package com.atguigu.gulimall.ware.service.impl;
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {
@Autowired
WareSkuDao wareSkuDao;
@Autowired
ProductFeignService productFeignService;
/**
* 添加商品库存 gulimall_wms.wms_ware_sku
* @param skuId
* @param wareId
* @param skuNum
*/
@Override
public void addStock(Long skuId, Long wareId, Integer skuNum) {
//1、判断如果还没有这个库存记录新增
List<WareSkuEntity> entities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
if(entities == null || entities.size() == 0){
WareSkuEntity skuEntity = new WareSkuEntity();
skuEntity.setSkuId(skuId);
skuEntity.setStock(skuNum);
skuEntity.setWareId(wareId);
skuEntity.setStockLocked(0);
//TODO 远程查询sku的名字,如果失败,整个事务无需回滚
//1、自己catch异常
//TODO 还可以用什么办法让异常出现以后不回滚?高级
try {
R info = productFeignService.info(skuId);
Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");
if(info.getCode() == 0){
skuEntity.setSkuName((String) data.get("skuName"));
}
}catch (Exception e){
}
wareSkuDao.insert(skuEntity);
}else{
wareSkuDao.addStock(skuId,wareId,skuNum);
}
}
}
WareSkuDao.addStock()
package com.atguigu.gulimall.ware.dao;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 商品库存
*
* @author daijinyu
* @email daijinyu@gmail.com
* @date 2024-09-09 22:38:50
*/
@Mapper
public interface WareSkuDao extends BaseMapper<WareSkuEntity> {
void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.gulimall.ware.dao.WareSkuDao">
<update id="addStock">
UPDATE `wms_ware_sku` SET stock=stock+#{skuNum}
WHERE sku_id=#{skuId} AND ware_id=#{wareId}
</update>
</mapper>
ProductFeignService.info()
package com.atguigu.gulimall.ware.feign;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("gulimall-product")
public interface ProductFeignService {
/**
* /product/skuinfo/info/{skuId}
*
*
* 1)、让所有请求过网关;
* 1、@FeignClient("gulimall-gateway"):给gulimall-gateway所在的机器发请求
* 2、/api/product/skuinfo/info/{skuId}
* 2)、直接让后台指定服务处理
* 1、@FeignClient("gulimall-product")
* 2、/product/skuinfo/info/{skuId}
*
* @return
*/
@RequestMapping("/product/skuinfo/info/{skuId}")
public R info(@PathVariable("skuId") Long skuId);
}
②改变采购项的状态
给定一个标志位flag(默认true),遍历所有采购项,只要有一个采购项的状态为采购失败,标志位就置为false;
更新采购项的状态,失败的更新为4-采购失败,成功的更新为3-已完成
③将成功采购的进行入库
根据当前item采购项的id查出采购项的详细信息(gulimall_wms.wms_purchase_detail采购项表:sku_id 采购商品id,ware_id 仓库id,sku_num 采购数量);
新建WareSkuServiceImpl.addStock() 方法,将以上3个参数传入,对gulimall_wms.wms_ware_sku商品库存表进行入库操作;
如果商品库存表中还没有这个库存记录时,需要先新增库存记录。根据商品id和仓库id查询商品库存表,如果查询为空进行新增操作:将以上3个参入存入对应字段,stock_locked锁定库存默认设为0,sku_name商品名字通过远程查询获:根据sku_id调用远程接口查询详细信息;
如果商品库存表中有这个库存记录时,更新库存数。WareSkuServiceImpl.addStock() 方法中新建wareSkuDao.addStock()方法,传入以上三个参数,更新gulimall_wms.wms_ware_sku商品库存表 stock 库存数;
①改变采购单状态
根据flag的值更新采购单状态,如果flag=true,采购单状态更新为3-已完成;如果flag=false,采购单状态更新为4-有异常;
14 分布式基础篇总结P101
附录:通用设置
日期格式化
application.yml
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
MyBatis分页配置
package com.atguigu.gulimall.ware.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.ware.dao")
@Configuration
public class WareMyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(true);
// // 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}