【Gulimall_1_2】谷粒商城分布式记录

在这里插入图片描述

分布式基础

分布式基础配置参考:link+官方link
所以环境都在Ubuntu16.04下进行

Docker

虚拟化容器技术。Docker基于镜像,可以秒级启动各种容器。每一种容器都是一个完整的运行
环境,容器之间互相隔离
在这里插入图片描述

1.安装:
#Docker的安装及学习[1]+[2]
PS:添加完用户组后,通过下述操作方可生效
方式一:刷新docker组 sudo newgrp docker
方式二:退出该用户重新登录 sudo su 切换到root再 su 用户名
docker安装完还不行,docker的目录放在系统根目录下可不行,镜像太占空间了,改位置
开机自启动服务:sudo systemctl enable docker.service

配置镜像阿里云加速器
针对Docker客户端版本大于 1.10.0 的用户
您可以通过修改daemon配置文件/etc/docker/daemon.json来使用加速器

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://nsgnmg58.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

2.拉镜像
dockerHub:link

mysql

mysql:docker pull mysql:5.7
在这里插入图片描述

运行容器

# --name指定容器名字 -v目录挂载 -p指定端口映射  -e设置mysql参数 -d后台运行
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

在这里插入图片描述

查看运行的容器
在这里插入图片描述

sudo docker ps
sudo docker ps -a
# 这两个命令的差别就是后者会显示  【已创建但没有启动的容器】

docker logs  容器ID(输入前几位方可) #docker查看日志

# 我们接下来设置我们要用的容器每次都是自动启动
sudo docker update mysql --restart=always
# 如果不配置上面的内容的话,我们也可以选择手动启动
sudo docker start mysql
# 如果要进入已启动的容器
sudo docker exec -it mysql /bin/bash

根据运行容器的ID或NAMES进入容器:sudo docker exec -it mysql bin/bash
每个docker都是一个小linux
在这里插入图片描述

Navicat连接

参考link

1 SQL脚本执行

指定数据库的制定字符集utf8mb4,其包含utf8

CREATE DATABASE  `gulimall-oms` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

IDEA中右键数据库-》
在这里插入图片描述
进入mysql容器,登录后通过自带的命令行source xxx.sql也是可以的
在这里插入图片描述在这里插入图片描述

2 SQL的comment 乱码

在这里插入图片描述
enca编码转换工具

1.安装
$sudo apt-get install enca
2.1查看文件编码
 $enca -L zh_CN 文件名 返回文件的编码 
2.2转换
命令格式如下
$enca -L 当前语言 -x 目标编码 文件名
例如要把当前目录下的所有文件都转成utf-8
$enca -L zh_CN -x utf-8 *

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

IDEA

link:换成JDK8方可
在这里插入图片描述
给IDEA安装两插件
在这里插入图片描述

Maven

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

稍微留意下版本发行日期,IDEA用的是2018.3.6

下载link

配置:

sudo gedit ~/.bashrc

文末追加

export M2_HOME=/home/xu/SOFT/apache-maven-3.5.4
export M2=$M2_HOME/bin
export PATH=$M2:$PATH

#刷新环境变量

source ~/.bashrc 

PS:#如果要配置系统级别的环境变量,则应该编辑以下文件sudo gedit /etc/profile

在这里插入图片描述

clean命令作用是:清理项目中target目录下文件。
compile命令作用是:将.java文件编译成 .class文件。
package命令作用是:将项目打包到target目录下。web 项目打包成:war文件。 java项目打包成:jar文件。

而点击父工程gulimall下的操作,可对整体进行
在这里插入图片描述

node

参考:link
下载link
添加源:目标版本14.15.1 LTS
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -

安装:sudo apt-get install -y nodejs
.bashrc 文末后缀

export NODE_HOME=/home/xu/SOFT/node/
export PATH=$PATH:$NODE_HOME/bin
export NODE_PATH=$NODE_HOME/lib/node_modules

在这里插入图片描述

npm install -g vue-cli 

在这里插入图片描述
发现全局安装的包,在命令行中都没反应,这也不奇怪,因为我将全局包路径改成了/home/xu/SOFT/node/node_global
在.bashrc追加路径方可:

export PATH="$PATH:/home/xu/SOFT/node/node_global/bin"

VSCode

下载link
在这里插入图片描述
安装:sudo dpkg -i code_1.51.1-1605051630_amd64.deb

配置插件
在这里插入图片描述
live-server插件:作为一个实时服务器实时查看开发的网页或项目效果。
vue 3 snippets插件:方便代码提示
vue插件:高亮显示.vue文件

Ubuntu下VSCode代码格式化的快捷键:ctrl+shift+I

在npm run dev时,虽然项目可以启动起来,但会额外:
在这里插入图片描述
解决办法:终端中依次执行

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

sudo sysctl --system

建立.vue模板:文件-> 首选项->用户代码片段->新建全局代码片段->输入vue(导入模板的命令)

{
	"生成vue魔板": {
		"prefix": "vue",
		"body": [
			"<!-- $1 -->",
			"<template>",
			"<div class='$2'>$5</div>",
			"</template>",
			"",
			"<script>",
			"//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)",
			"//例如:import 《组件名称》 from '《组件路径》';",
			"",
			"export default {",
			"//import引入的组件需要注入到对象中才能使用",
			"components: {},",
			"props: {},",
			"data() {",
			"//这里存放数据",
			"return {",
			"",
			"};",
			"},",
			"//监听属性 类似于data概念",
			"computed: {},",
			"//监控data中的数据变化",
			"watch: {},",
			"//方法集合",
			"methods: {",
			"",
			"},",
			"//生命周期 - 创建完成(可以访问当前this实例)",
			"created() {",
			"",
			"},",
			"//生命周期 - 挂载完成(可以访问DOM元素)",
			"mounted() {",
			"",
			"},",
			"beforeCreate() {}, //生命周期 - 创建之前",
			"beforeMount() {}, //生命周期 - 挂载之前",
			"beforeUpdate() {}, //生命周期 - 更新之前",
			"updated() {}, //生命周期 - 更新之后",
			"beforeDestroy() {}, //生命周期 - 销毁之前",
			"destroyed() {}, //生命周期 - 销毁完成",
			"activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发",
			"}",
			"</script>",
			"<style scoped>", //"<style lang='scss' scoped>",
			//"//@import url($3); 引入公共css类",
			"$4",
			"</style>"
		],
		"description": "Log output to console"
	}
}

导入模板:新建.vue文件->内容中键入vue -> tab

忽略Eslint检查:.eslintignore中追加

**/*.js
**/*.vue

清除火狐缓存快捷键:Ctrl+Shift+Delete

通过前端vue获取到的
在这里插入图片描述
代办事项备注,以便回补。在代码中加入 //Todo //备注信息方可
在这里插入图片描述

html中特殊字符表示

在这里插入图片描述

设计思想

点1:

1、Controller:处理请求,接受和校验数据
2、Service接受controller传来的数据,进行业务处理
3、Controller接受Service处理完的数据,封装页面指定的vo

点2:vo策略
当有新增字段时,我们往往会在entity实体类中新建一个字段,并标注数据库中不存在该字段,然而这种方式并不规范
在这里插入图片描述点3:To策略
利用Feign进行远程调用时,调用者A将制定类型的To对象放入请求体中,以json发送到另一个服务B,在B中为了方便可以利用To类型对象将请求体中内容接收。PS:B不一定非得用To类型,只要保持与传输来的json中字段吻合方可。

Tools

1 Postman

ubuntu的firefox没有Postman,只有Postwoman.有点小难受,还是装下吧
link
有一点需要注意的:sudo ln -s /home/xu/SOFT/Postman/Postman /usr/bin/postman
为使能在任意位置的命令行执行,建立软连接要用全路径

Bug

三级菜单教程bug

现象:,当你给它原本的菜单添加子菜单时大部分是可以的,但给我们自己新建的菜单添加子菜单确是不好使的。注意不要被现象蒙骗了(新建的子菜单catId默认自增,很大了)
分析:经过核查,数据库中数据是保存了,但返回数据的时候,没设置为对应的children.问题在于过滤器,categoryEntity.getParentCid() == root.getCatId())
实体类中catId,parentCid都是Long对象类型。
Byte、Short、Integer、Long四种包装类默认创建了数值为[-128,127]的相应类型的缓存数据,但是超出此范围仍会创建新的对象。
改为:categoryEntity.getParentCid().equals(root.getCatId())

自动注入

在自动注入Dao时

@Autowired
AttrAttrgroupRelationDao relationDao;

出现红色波浪线还有提示:
Could not autowire. No beans of ‘AttrAttrgroupRelationDao’ type found. less… (Ctrl+F1)
Inspection info:Checks autowiring problems in a bean class.
这个不是错误
在这里插入图片描述

分布式高级

配置可参考该栏的其余博客

vim的基本使用

参考link
i进入输入模式 
ESC退出,进入命令模式

:set number显示行号
:wq 保存并退出

解压zip中文乱码

unzip -O CP936 代码.zip 

nginx+网关联调

/mydata/nginx/logs

2020/12/24 15:00:22 [error] 6#6: *2 open() “/usr/share/nginx/html/static/static/index/css/GL.css” failed (2: No such file or directory)

location用法link

意图:动静分离,静态资源直接从nginx获取
动态资源利用nginx将本地域名gulimall.com 负载到 网关 192.168.1.113:6060,网关中通过Host断言交给gulimall-product微服务,但,。,。
在这里插入图片描述
排查:
1.访问nginx静态资源:
http://gulimall.com/static/index/img/logo.jpg
http://gulimall.com/static/index/img/img_05.png
正常则说明nginx起来了
2.1网关直接访问gulimall-product的后台服务(面向商家)
http://192.168.1.113:6060/api/product/category/info/1
正常则说明网关的后台服务断言没毛病
2.2nginx经过网关间接访问gulimall-product的后台服务
http://gulimall.com/api/product/category/info/1
正常则说明nginx可以代理到网关
3.1直接访问前台服务(面向客户)
http://192.168.1.113:10000/
正常则说明前台服务逻辑没问题

但http://gulimall.com/不好使,那就只有两种可能:
(1)请求中根本没有host -> 考虑trace请求 而Postman中没有trace参考curl的link

务必避免下面错误:将单引号改为双引号
在这里插入图片描述
(2)网关的host断言写的有问题 PS:本例就是因为该问题,手抖删了个名字,尴尬

		- id: product_host_route
          uri: lb://gulimall-product # lb(load balance)代表从注册中心获取服务
          predicates:
            - Host=**.gulimall.com, gulimall.com   #根据目标服务主机host判断,所以需要放在后面

Seata

SEATA AT 模式需要 UNDO_LOG 表

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

在gulimall-common中添加该依赖,发现其自动依赖了
在这里插入图片描述
link下载对应seata-all版本的TC服务

定制跳转

省得为啥也不干的跳转再写“空”controller方法,但这里映射的只能通过GET方法访问

@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
//        registry.addViewController("/login.html").setViewName("login");
        registry.addViewController("/reg.html").setViewName("reg");
    }
}

用springMVC的viewController,通过这个viewName去templates下找页面

设计思路

1查询完整三级分类数据分类数据时
在这里插入图片描述

List<CategoryEntity> selectList = baseMapper.selectList(null); //先查询全部,避免后续的多次查询

利用springcache,结果存缓存避免每次都去查

@Cacheable(value = {"category"},key = "#root.methodName")
@Override
public Map<String, List<Catelog2Vo>> getCatelogJson(){

2 秒杀服务的信号量机制,商品随机码
2.1秒杀商品上架
在这里插入图片描述

这里涉及两个幂等性redis存储,一个是活动的,一个是活动关联商品的。存储了商品随机码,还有一个点是将库存设置为了信号量,缓存的图示为:
在这里插入图片描述

在这里插入图片描述

{
  "endTime": 1642039200000,
  "promotionSessionId": 1,
  "randomCode": "22b027c349de4e6fa90ee426f35dd6f6",
  "seckillCount": 23,
  "seckillLimit": 2,
  "seckillPrice": 3453,
  "seckillSort": 1,
  "skuId": 26,
  "skuInfoVo": {
    "brandId": 1,
    "catalogId": 225,
    "price": 5999.0,
    "saleCount": 0,
    "skuDefaultImg": "https://gulimallyfxu.oss-cn-shanghai.aliyuncs.com/2020-12-22/3982918c-315c-4eee-8267-16fcf30bb3a3_8bf441260bffa42f.jpg",
    "skuId": 26,
    "skuName": "华为 HUAWEI Mate",
    "skuSubtitle": "麒麟9000E SoC芯片 5000万超感知徕卡电影影像 有线无线双超级快充",
    "skuTitle": "华为 mate30 黑色 256G",
    "spuId": 23
  },
  "startTime": 1610496000000
}

PS:在商品详情页刷新时,如果当前刷新页面的时间不在秒杀活动时间范围内,返回页面数据(包含从缓存中获取的之前上架的秒杀信息)中的商品随机码置为null。
2.2返回当前时间可以参与的秒杀商品信息 
SeckillController
在这里插入图片描述
2.3开始秒杀
在这里插入图片描述
核对商品随机码,try获取信号量,成功后发消息队列。具体参见link

3 微博登录
在这里插入图片描述
注册时验证码防重刷,也是利用redis机制,限定60s内若是多次点击的话就发一次,缓存的示意:
在这里插入图片描述

参考这篇文章的短信服务处代码link

4 加入购物车时,重定向防止表单重复提交

foward转发请求方式不变
redirect重定向 但获取不到model中放的数据(请求域中)
RedirectAttributes redirectAttributes 重定向携带数据重定向也能共享,但似乎是以FlashMap实体方式存储在session中的
 redirectAttributes.addFlashAttribute("errors", errors); 只能取一次

com/atguigu/gulimall/cart/controller/CartController.java

    @GetMapping("/addToCart")
    public String addToCart(@RequestParam("skuId") Long skuId,
                            @RequestParam("num") Integer num,
                            RedirectAttributes res) throws ExecutionException, InterruptedException {

        cartService.addToCart(skuId,num);
        //将数据拼接到url后面
        res.addAttribute("skuId",skuId); //这里添加的数据会自动放在url后面
        // 重定向到对应的地址
        return "redirect:http://cart.gulimall.com/addToCartSuccess.html"; //解决重复提交
    }

    /**
     * 跳转到成功页面
     */
    @GetMapping("/addToCartSuccess.html")
    public String addToCartSuccessPage(@RequestParam("skuId") Long skuId, Model model) {
        // 重定向到成功页面,再次查询购物车数据
        CartItem item = cartService.getCartItem(skuId);
        model.addAttribute("item",item);
        return "success";
    }

值得注意的是,没有直接return “success”,而是return “redirect:http://cart.gulimall.com/addToCartSuccess.html”;
5 订单防重令牌

  // 先是再页面中生成一个随机码把他叫做token先存到redis中,然后放到对象中在页面进行渲染。
  // 用户提交表单的时候,带着这个token和redis里面去匹配如果一直那么可以执行下面流程。
  // 匹配成功后再redis中删除这个token,下次请求再过来的时候就匹配不上直接返回

5.1cartList.html点击去结算
在这里插入图片描述
生成结算的详情,并给redis存储订单防重令牌,防止订单重复提交

// TODO 5.防重令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        orderConfirmVo.setOrderToken(token);
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX +memberRespVo.getId(), token, 10, TimeUnit.MINUTES);

缓存的图示为:
在这里插入图片描述

5.2 confirm.html点击提交订单
在这里插入图片描述上图是订单超时未支付关闭的情况:
1是order.release.order.queue
2是stock.release.stock.queue

// 1. 验证令牌 [必须保证原子性] 返回 0 or 1
        // 0 令牌删除失败 1删除成功
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        String orderToken = vo.getOrderToken();

        // 原子验证令牌 删除令牌
        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRsepVo.getId()), orderToken);
        if(result == 0L){
            // 令牌验证失败
            submitVo.setCode(1);
        }else{
            // 令牌验证成功
。。。
                // 远程锁库存
                R r = wmsFeignService.orderLockStock(lockVo);
                。。。
					//Todo 订单创建成功,发送消息给MQ
                    rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder());

监听订单未支付关闭:com/atguigu/gulimall/order/listener/OrderCloseListener.java,此时改变订单状态为已取消,并会发消息主动解库存

 //给MQ发消息转到库存释放队列
 rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);

监听器com/atguigu/gulimall/ware/listener/StockReleaseListener.java收到库存解锁消息,通过一系列条件核实若确实需要解采取的是反向补偿的策略(原本锁了多少,现在减多少)

Bug

老神奇的问题

  • nested exception is java.lang.IllegalStateException: Cannot create a session after the response has been committed

经典操作

流式组装过滤

List<SkuImagesEntity> imagesEntities = items.stream()  //此处的items是List   若是数组的话得先Arrays.asList(items)
	.map(item -> {  //map是个组装工人
		SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
		    skuImagesEntity.setSkuId(skuId);
		    skuImagesEntity.setImgUrl(item.getImgUrl());
		    skuImagesEntity.setDefaultImg(item.getDefaultImg());
		    return skuImagesEntity;})
	.filter(entity->{
	    //返回true就是需要,false就是剔除
	    return !StringUtils.isEmpty(entity.getImgUrl());
	}).collect(Collectors.toList());

属性拷贝

BeanUtils.copyProperties(src, target) //只拷贝名匹配的字段

获取HashMap存储的数据

com/atguigu/common/utils/R.java

public <T> T getData(String key, TypeReference<T> typeReference){
	// get("data") 默认是map类型 所以再由map转成string再转json
	Object data = get(key);
	return JSON.parseObject(JSON.toJSONString(data), typeReference);
}

StringRedisTemplate

String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
        if (!StringUtils.isEmpty(catelogJSON)) {
String uuid = UUID.randomUUID().toString();
// set lock uuid EX 300 NX
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);

String token = UUID.randomUUID().toString().replace("-", “”);

// 通过使用lua脚本进行原子性删除
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//删除锁
Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
List<String> range = stringRedisTemplate.opsForList().range(key, 0, 100);
BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
List<String> list = hashOps.multiGet(range);

SpringMVC的入参对象

@RequestMapping("/list.html")
    public String lisyPage(SearchParam param, Model model, HttpServletRequest request) {  //SpringMVC自动将页面提交过来的所有请求查询参数封装成指定对象
    param.set_queryString(request.getQueryString()); //request.getQueryString()中的中文被编码

日志打印

@Slf4j

log.info("登录成功:用户:{}",data.toString());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空•物语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值