华为云云耀云服务器L实例评测|基于canal缓存自动更新流程 SpringBoot项目应用案例和源码

在这里插入图片描述

前言

最近华为云云耀云服务器L实例上新,也搞了一台来玩,期间遇到各种问题,在解决问题的过程中学到不少和运维相关的知识。

在之前的博客中,介绍过canal的安装和配置,参考博客

  • 拉取创建canal镜像配置相关参数 & 搭建canal连接MySQL数据库 & spring项目应用canal初步

本篇博客给出了canal项目应用的案例,详细介绍基于canal实现数据库和缓存同步的流程,并给出了核心diamante的源码。

在这里插入图片描述

其他相关的华为云云耀云服务器L实例评测文章列表如下:

  • 初始化配置SSH连接 & 安装MySQL的docker镜像 & 安装redis以及主从搭建 & 7.2版本redis.conf配置文件

  • 安装Java8环境 & 配置环境变量 & spring项目部署 &【!】存在问题未解决

  • 部署spring项目端口开放问题的解决 & 服务器项目环境搭建MySQL,Redis,Minio…指南

  • 由于自己原因导致MySQL数据库被攻击 & MySQL的binlog日志文件的理解

  • 认识redis未授权访问漏洞 & 漏洞的部分复现 & 设置连接密码 & redis其他命令学习

  • 拉取创建canal镜像配置相关参数 & 搭建canal连接MySQL数据库 & spring项目应用canal初步

  • Docker版的Minio安装 & Springboot项目中的使用 & 结合vue进行图片的存取

  • 在Redis的Docker容器中安装BloomFilter & 在Spring中使用Redis插件版的布隆过滤器

在这里插入图片描述

  • Elasticsearch的Docker版本的安装和参数设置 & 端口开放和浏览器访问

  • Elasticsearch的可视化Kibana工具安装 & IK分词器的安装和使用

  • Elasticsearch的springboot整合 & Kibana进行全查询和模糊查询

文章目录

  • 前言
  • 引出
  • 基于canal缓存同步更新
    • 整体的流程
  • 相关代码和流程
    • 1.canal通道的配置
    • 2.前端查询的业务代码
    • 3.数据库数据更新
    • 4.缓存更新前端展示
  • 核心代码源码
    • 1.配置yml和配置类
    • 2.canal自动更新代码
    • 3.查询的业务层service代码
    • 4.主启动类
    • 5.前端vue代码
  • 总结

引出


1.介绍基于canal实现数据库和缓存同步的流程;
2.给出了核心diamante的源码;

基于canal缓存同步更新

整体的流程

启动spring项目时,同步启动缓存自动更新,如果数据库的相关表格数据发生变化,canal通过就会监听到,然后更新缓存redis中的相应数据

在这里插入图片描述

哪些数据从缓存中取?——不经常更新的数据:比如公司的部门,仓库等;

在项目启动时,启动了canal,canal用来监听数据库的变化;

业务逻辑:前端请求相关数据–> 问Redis要

(1)如果redis里面有,则返回给前端;

(2)如果redis里面没有,则从数据库查询,并且存到redis里面,返回给前端;

(3)如果数据库发生更新,canal监听到修改,同步更新到Redis里面,保证缓存和数据库一致;

在这里插入图片描述

相关代码和流程

1.canal通道的配置

在这里插入图片描述

缓存自动更新,读取配置文件中的ip和端口号

在这里插入图片描述

是否开启缓存自动更新,从配置文件中读取配置;

在这里插入图片描述

2.前端查询的业务代码

在这里插入图片描述

在数据库数据没有更新时,获取缓存中的数据

在这里插入图片描述

3.数据库数据更新

如果数据库的数据更新,canal监听到,发现是缓存对应的表,并且是对应的字段发生变化,则进行缓存的自动更新

在这里插入图片描述

缓存自动同步更新

在这里插入图片描述

存到Redis里面的最新的数据

在这里插入图片描述

4.缓存更新前端展示

缓存更新后,前端再次查询,获得最新的数据

在这里插入图片描述

核心代码源码

1.配置yml和配置类

配置yml文件

server:port: 10050## 是否启用安全框架 true为开启,false为关闭
security:isOpen: true## 是否开启canal管道,true为开启,false为关闭
canal:isOpen: true# canal的相关配置
canalConfig:host: 124.70.138.34port: 11111

在这里插入图片描述

Redis存Java对象的配置类

package com.tianju.fresh.config.redis;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisSerializeConfig {@Beanpublic RedisTemplate redisTemplateInit(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);//设置序列化Key的实例化对象redisTemplate.setKeySerializer(new StringRedisSerializer());//设置序列化Value的实例化对象redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());/**** 设置Hash类型存储时,对象序列化报错解决*/redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());return redisTemplate;}
}

2.canal自动更新代码

canal自动更新的代码,用canal管道监听MySQL数据变化,自动更新redis缓存

package com.tianju.fresh.config.redis;import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import com.baomidou.mybatisplus.annotation.TableField;
import com.tianju.fresh.entity.common.GoodsTypeVo;
import com.tianju.fresh.entity.common.WarehouseVo;
import com.tianju.fresh.entity.goods.GoodsType;
import com.tianju.fresh.mapper.goods.GoodsTypeMapper;
import com.tianju.fresh.mapper.warehouse.StorehouseMapper;
import com.tianju.fresh.service.common.CommonStaticMethod;
import com.tianju.fresh.util.Constance;
import com.tianju.fresh.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;/*** 用canal管道监听MySQL数据变化,自动更新redis缓存*/
@Slf4j
@Component
public class AutoUpdateRedis {@Value("${canalConfig.host}")private String host;@Value("${canalConfig.port}")private Integer port;@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Autowiredprivate GoodsTypeMapper typeMapper;@Autowiredprivate StorehouseMapper storehouseMapper;@Autowiredprivate RedisUtil redisUtil;public void run() {// 创建链接final InetSocketAddress HOST = new InetSocketAddress(host,port);
//        final InetSocketAddress HOST = new InetSocketAddress("192.168.111.130",11111);CanalConnector connector = CanalConnectors.newSingleConnector(HOST, "example", "", "");int batchSize = 1000;int emptyCount = 0;try {connector.connect();connector.subscribe(".*\\..*");connector.rollback();int totalEmptyCount = 120;
//            while (emptyCount < totalEmptyCount) {while (true) {Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据long batchId = message.getId();int size = message.getEntries().size();if (batchId == -1 || size == 0) {emptyCount++;if(emptyCount % 100==0 || emptyCount==1){System.out.println("empty count : " + emptyCount);}try {Thread.sleep(1000);} catch (InterruptedException e) {}} else {emptyCount = 0;// System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);printEntry(message.getEntries());}connector.ack(batchId); // 提交确认// connector.rollback(batchId); // 处理失败, 回滚数据}//            System.out.println("empty too many times, exit");} finally {connector.disconnect();}}private void printEntry(List<Entry> entrys) {for (Entry entry : entrys) {if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {continue;}RowChange rowChage = null;try {rowChage = RowChange.parseFrom(entry.getStoreValue());} catch (Exception e) {throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),e);}EventType eventType = rowChage.getEventType();System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),eventType));String tableName = entry.getHeader().getTableName();if (Constance.LISTEN_TAB_NAMES.contains(tableName)){for (RowData rowData : rowChage.getRowDatasList()) {if (eventType == EventType.DELETE){// 删除之前log.debug("-------删除之前before");changeBefore(rowData.getBeforeColumnsList());log.debug("-------删除之后after");// 传修改的表名,和常量中的map对比,从而对应的redis里的key
//                        changeAfter(rowData.getAfterColumnsList(),tableName);}else if (eventType == EventType.INSERT){// 插入之前log.debug("-------插入之前before");changeBefore(rowData.getBeforeColumnsList());log.debug("-------插入之后after");// 传修改的表名,和常量中的map对比,从而对应的redis里的key
//                        changeAfter(rowData.getAfterColumnsList(),tableName);}else {// 修改之前log.debug("-------修改之前before");changeBefore(rowData.getBeforeColumnsList());log.debug("-------修改之后after");// 传修改的表名,和常量中的map对比,从而对应的redis里的keychangeAfter(rowData.getAfterColumnsList(),tableName);}}}}}/*** 数据库更新之前* @param columns*/private  void changeBefore(List<Column> columns) {for (Column column : columns) {System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());}}private  void changeAfter(List<Column> columns, String tableName) {for (Column column : columns) {System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());// 如果是商品类别表变化if ("name".equals(column.getName()) && Constance.GOODS_TYPE_TAB_NAME.equals(tableName)){ // 默认是名称那一列变化才更新缓存// 先删除key,再设置好新的keyMap tabNameToRedisKey = Constance.getTabNameToRedisKey();String redisKey = (String) tabNameToRedisKey.get(tableName);redisTemplate.delete(redisKey);// TODO:设置新的keyList<GoodsTypeVo> list = CommonStaticMethod.getGoodsTypeVos(typeMapper);redisUtil.saveObjectToRedis(Constance.GOODS_TYPE_REDIS_KEY, list);break;}// 如果是仓库那张表变化 storehouseif ("name".equals(column.getName()) && Constance.STOREHOUSE_TAB_NAME.equals(tableName)){ // 默认是名称那一列变化才更新缓存// 先删除key,再设置好新的keyMap tabNameToRedisKey = Constance.getTabNameToRedisKey();String redisKey = (String) tabNameToRedisKey.get(tableName);redisTemplate.delete(redisKey);// 设置新的key,存到redis里面List<WarehouseVo> list = CommonStaticMethod.getStorehouseVos(storehouseMapper);redisUtil.saveObjectToRedis(Constance.STOREHOUSE_REDIS_KEY,list);break;}}}
}

在这里插入图片描述

redis的工具类

package com.tianju.fresh.util;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;@Component
public class RedisUtil {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;public void  saveObjectToRedis(String key,Object json){redisTemplate.opsForValue().set(key,json);}/*** 从redis里面获取json对象,如果没有,返回null* @param key* @return*/public Object getJsonFromRedis(String key){return redisTemplate.opsForValue().get(key);}}

监听数据库表,列名的常量类

package com.tianju.fresh.util;import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 专门放各种常量*/
public interface Constance {// 设置哪些数据库表需要监听,比如单位unit,仓库warehouse,商品类型等List<String> LISTEN_TAB_NAMES = Arrays.asList("goods_type","unit_commodity","warehouse_center","warehouse_tab");String GOODS_TYPE_TAB_NAME = "goods_type";String GOODS_TYPE_REDIS_KEY = "goodsTypeVo";String UNIT_TAB_NAME = "unit_commodity";String UNIT_REDIS_KEY = "unitVo";String WAREHOUSE_TAB_NAME = "warehouse_center";String WAREHOUSE_REDIS_KEY = "warehouseCenterVo";String STOREHOUSE_TAB_NAME = "warehouse_tab";String STOREHOUSE_REDIS_KEY = "storehouseVo";static Map getTabNameToRedisKey(){Map<String,String> map = new HashMap<>();map.put(GOODS_TYPE_TAB_NAME,"goodsTypeVo");map.put("unit_commodity","unitVo");map.put("warehouse_center","warehouseCenterVo");map.put("warehouse_tab","storehouseVo");return map;}
}

3.查询的业务层service代码

在这里插入图片描述

    @Overridepublic List<WarehouseVo> findStorehouse() {
//        List<Storehouse> storehouses = storehouseMapper.selectList(null);
//        List<WarehouseVo> list = new ArrayList<>(storehouses.size());
//        storehouses.forEach(s->{
//            list.add(new WarehouseVo(s.getId()+"", s.getName()));
//        });// 是否有小仓库的redis的keyBoolean hasKey = redisTemplate.hasKey(Constance.STOREHOUSE_REDIS_KEY);if (hasKey){ // 如果有,走缓存List<WarehouseVo> list = (List<WarehouseVo>) redisTemplate.opsForValue().get(Constance.STOREHOUSE_REDIS_KEY);log.debug("get storehouseVo from redis: "+list);return list;}// 如果没有从数据库查询,存到redis里面List<WarehouseVo> list = CommonStaticMethod.getStorehouseVos(storehouseMapper);log.debug("get storehouseVo from mysql: "+list);redisUtil.saveObjectToRedis(Constance.STOREHOUSE_REDIS_KEY, list);return list;}

4.主启动类

主启动类实现implements CommandLineRunner方法,启动canal通道,进行监听数据库的变化,实现缓存同步更新

package com.woniu.fresh;import com.woniu.fresh.config.redis.AutoUpdateRedis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@Slf4j
@EnableAspectJAutoProxy // 让动态代理生效
@EnableScheduling // 让定时任务生效
public class FreshApp implements CommandLineRunner {public static void main(String[] args) {SpringApplication.run(FreshApp.class);}@Autowiredprivate AutoUpdateRedis autoUpdateRedis;@Value("${canal.isOpen}")private Boolean isCanal;@Overridepublic void run(String... args) throws Exception {if (isCanal){log.debug(">>>>>启动缓存自动更新");autoUpdateRedis.run();}}
}

5.前端vue代码

<template><div><el-row><el-col :span="24"><el-form :inline="true" label-width="80px"><el-form-item label="领料单号"><el-input v-model="findGoodsParams.shoppingNo"></el-input></el-form-item><el-form-item label="领料数量≥"><el-input v-model="findGoodsParams.nums"></el-input></el-form-item><el-form-item label="原料名称"><el-input v-model="findGoodsParams.rawName"></el-input></el-form-item><el-form-item label="领料仓库"><el-select v-model="findGoodsParams.warehouseId" placeholder="请选择"><el-option v-for="item in commondata.storehouse" :key="item.value" :label="item.label":value="item.value"></el-option></el-select></el-form-item><el-form-item label="状态"><el-select v-model="findGoodsParams.status" placeholder="请选择"><el-option v-for="item in commondata.status" :key="item.value" :label="item.label":value="item.value"></el-option></el-select></el-form-item><el-form-item><el-button type="primary" @click="findGoods">查询</el-button><el-button type="success" @click="reFindGoods">重置</el-button></el-form-item></el-form></el-col></el-row><el-row><el-col :span="24"><el-button type="success" @click="addGoodsBtn">新增</el-button><el-button type="warning" icon="el-icon-edit" @click="batchDelete">批量审批</el-button><el-button type="primary" icon="el-icon-magic-stick" @click="batchPickDoing">批量领取中</el-button><el-button type="primary" icon="el-icon-shopping-cart-full" @click="batchPickDown">批量已领完</el-button></el-col></el-row><el-row><el-col :span="24"><el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange"><el-table-column type="selection" width="55"></el-table-column><el-table-column prop="pickNo" label="领料单号" width="240"></el-table-column><el-table-column prop="name" label="原材料名" width="100"></el-table-column><el-table-column prop="nums" label="领料数量" width="80"></el-table-column><el-table-column prop="unit" label="单位" width="80"></el-table-column><el-table-column prop="warehouse" label="领料仓库" width="180"></el-table-column><el-table-column prop="emp" label="仓管员" width="150"></el-table-column><el-table-column prop="status" label="状态" width="80"><template slot-scope="scope"><span v-if="scope.row.status == '0'" style="color: red;">未审批</span><span v-if="scope.row.status == '1'" style="color: rgb(9, 209, 109);">已审批</span><span v-if="scope.row.status == '2'" style="color: rgb(44, 39, 205);">已领取</span><span v-if="scope.row.status == '3'" style="color: rgb(173, 16, 157);">已领完</span><!-- {{ scope.row.status == 1 ? '已上架' : '下架' }} --></template></el-table-column><el-table-column label="操作"><template slot-scope="scope"><el-button type="primary" icon="el-icon-edit" circle@click="loadBtn(scope.row.id)">审批</el-button></template></el-table-column></el-table></el-col></el-row><el-row><el-col :span="24"><div class="block"><el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange":current-page.sync="currentPage" :page-sizes="[3, 5, 10]" :page-size="3"layout="total,sizes, prev, pager, next" :total=total></el-pagination></div></el-col></el-row><!-- 新增原材料入库弹窗 ******* --><el-dialog title="添加原材料单" :visible.sync="b"><el-form><el-row><el-col :span="12"><el-form-item label="原料名称" clearable><el-select v-model="shoppingNoId" placeholder="请选择"style="width: 355px;margin-right:px;margin-left:px" @change="selectBuyNo"><el-option v-for="item in shoppingIdToNo" :key="item.label" :label="item.label":value="item.label"></el-option></el-select></el-form-item><el-form-item label="数量" :label-width="formLabelWidth"><el-input v-model="goods.nums" autocomplete="off"></el-input></el-form-item><el-form-item label="单位" clearable><el-select v-model="goods.unit" placeholder="请选择商品单位"style="width: 355px;margin-right:px;margin-left:px"><el-option v-for="item in commondata.unit" :key="item.value" :label="item.label":value="item.value"></el-option></el-select></el-form-item><el-form-item label="中心仓库" clearable><el-select v-model="goods.warehouse" placeholder="请选择" disabledstyle="width: 355px;margin-right:px;margin-left:px"><el-option v-for="item in commondata.warehouse" :key="item.value" :label="item.label":value="item.value"></el-option></el-select></el-form-item><el-form-item label="领料仓库" clearable><el-select v-model="goods.storehouse" placeholder="请选择"style="width: 355px;margin-right:px;margin-left:px"><el-option v-for="item in commondata.storehouse" :key="item.value" :label="item.label":value="item.value"></el-option></el-select></el-form-item></el-col></el-row></el-form><div slot="footer" class="dialog-footer"><el-button @click="b = false">取 消</el-button><el-button type="primary" @click="addGoods()">确 定</el-button></div></el-dialog></div>
</template><script>
export default {data() {return {findGoodsParams: {"pickNo": "", "name": "", "nums": "", "storehouseId": "", "status": ""},// 批量删除的iddeleteIds: [],tableData: [{"id": 1,"pickNo": "PICK2023090818003927","name": "富士苹果","nums": "350.00","unit": "千克","warehouse": "南京江宁生鲜1号仓库","storehouseId": 2,"emp": "领料操作员1李四","status": "0"}],// 分页相关的参数total: 10,currentPage: 1,pageSize: 3,options: [{ value: '0', label: '未审批' },{ value: '1', label: '审批通过' },{ value: '2', label: '已领取' },{ value: '3', label: '已领完' },],commondata: {"storehouse": [{"value": 1, "label": "南京中心仓库南京总统府"},],"status": [{ "value": 0, "label": "未审批" },{ "value": 1, "label": "审批通过" }]},// 新增商品弹窗控制变量b: false,// 新增的入库信息 goods shoppingNoIdgoods: {"name":"富士苹果","nums":"200.56","unit":"1",// 中心仓库"warehouse":"1", // 目标仓库"storehouse":"2"},formLabelWidth: '100px',// 新增原材料入库清单addRawTab:[{"name": {"unit": "","warehouse": {"1": 1000.20}}},],// 和上面进行比对shoppingNoId:"",// 选择采购清单的下拉框shoppingIdToNo: [{"label": "BUY2023091317093927"},],}},methods: {handleSizeChange(val) {console.log(`每页 ${val} 条`);this.pageSize = valthis.findGoods()},handleCurrentChange(val) {console.log(`当前页: ${val}`);this.pageNum = valthis.findGoods()},findGoods() {let params = {}params.pageNum = this.currentPageparams.pageSize = this.pageSizeparams.param = this.findGoodsParamsconsole.log(params)this.$axios.post("/api/warehouse/findPagePickRaw", params).then(response => {let resp = response.dataconsole.log(resp)if (resp.resultCode.code == 20000) {this.tableData = resp.results.listthis.total = resp.results.totalthis.currentPage = resp.results.pageNumthis.pageSize = resp.results.pageSize}})},reFindGoods() {let params = {}this.findGoodsParams = {"pickNo": "", "name": "", "nums": "", "storehouseId": "", "status": ""},params.pageNum = this.currentPageparams.pageSize = this.pageSizeparams.param = this.findGoodsParamsconsole.log(params)this.$axios.post("/api/warehouse/findPagePickRaw", params).then(response => {let resp = response.dataconsole.log(resp)if (resp.resultCode.code == 20000) {this.tableData = resp.results.listthis.total = resp.results.totalthis.currentPage = resp.results.pageNumthis.pageSize = resp.results.pageSize}})},handleSelectionChange(val) {this.deleteIds = []console.log(val);val.forEach(e => this.deleteIds.push(e.id))},// 批量修改领取中batchPickDoing() {console.log(this.deleteIds)this.$axios.put("/api/warehouse/batchDoingPickMaterial", this.deleteIds).then(response => {let resp = response.dataconsole.log(resp)if (resp.resultCode.code == 20000) {this.findGoods()} else {alert(resp.results)}})},// 批量修改已领完batchPickDown() {console.log(this.deleteIds)this.$axios.put("/api/warehouse/batchDownPickMaterial", this.deleteIds).then(response => {let resp = response.dataconsole.log(resp)if (resp.resultCode.code == 20000) {this.findGoods()} else {alert(resp.results)}})},// 批量审批通过batchDelete() {console.log(this.deleteIds)this.$axios.put("/api/warehouse/batchPassPickMaterial", this.deleteIds).then(response => {let resp = response.dataconsole.log(resp)if (resp.resultCode.code == 20000) {this.findGoods()} else {alert(resp.results)}})},// 逐一审批通过loadBtn(val) {console.log(val)const deleteIds = []deleteIds.push(val)console.log(deleteIds)this.$axios.put("/api/warehouse/batchPassPickMaterial", deleteIds).then(response => {let resp = response.dataconsole.log(resp)if (resp.resultCode.code == 20000) {this.findGoods()} else {alert(resp.results)}})},// 获取公共数据getCommonData() {this.$axios.get("/api/common/pickCommon").then(response => {let resp = response.dataif (resp.resultCode.code == 20000) {this.commondata = resp.resultsconsole.log("#############")console.log(this.commondata)}})},addGoodsBtn() {this.b = truethis.goods = {} this.shoppingIdToNo = []this.$axios.get('/api/warehouse/findRawNames').then(res => {if (res.data.resultCode.code == 20000) {this.addRawTab = res.data.resultsconsole.log(this.addRawTab)this.addRawTab.forEach(r=>{this.shoppingIdToNo.push({"label": Object.keys(r)[0]})})console.log(this.shoppingIdToNo)}})},// 弹出的新增窗口的添加addGoods() {console.log("#############")console.log(this.goods)this.$axios.post('/api/warehouse/addPickRaw', this.goods).then(res => {console.log("&&&&&")console.log(res.data)if (res.data.resultCode.code == 20000) {alert('添加成功')this.findGoods()}else{alert('添加失败')}}),this.b = false},// 绑定下拉框的选择事件selectBuyNo(){console.log("change")const goodsTemp = this.addRawTab.filter(r=> Object.keys(r)[0] == this.shoppingNoId)[0]console.log(goodsTemp)const nameTmp = Object.keys(goodsTemp)[0]const nextTmp = Object.values(goodsTemp)[0].warehouseconst keyTmp = Object.keys(nextTmp)[0]console.log(nextTmp)this.goods={name:nameTmp,nums:nextTmp[keyTmp],unit:Object.values(goodsTemp)[0].unit+"",warehouse:Object.keys(nextTmp)[0]}console.log(this.goods)},},created() {this.findGoods()this.getCommonData()}}</script>

总结

1.介绍基于canal实现数据库和缓存同步的流程;
2.给出了核心diamante的源码;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/149532.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

《Jetpack Compose从入门到实战》第一章 全新的 Android UI 框架

书籍源码 Compose官方文档 《Jetpack Compose从入门到实战》第一章 全新的 Android UI 框架 《Jetpack Compose从入门到实战》 第二章 了解常用UI组件 《Jetpack Compose从入门到实战》第三章 定制 UI 视图 《Jetpack Compose从入门到实战》第八章 Compose页面 导航 《Jet…

【Overload游戏引擎分析】画场景网格的Shader

Overload引擎地址&#xff1a; GitHub - adriengivry/Overload: 3D Game engine with editor 一、栅格绘制基本原理 Overload Editor启动之后&#xff0c;场景视图中有栅格线&#xff0c;这个在很多软件中都有。刚开始我猜测它应该是通过绘制线实现的。阅读代码发现&#xff0…

JAVA面经整理(8)

一)为什么要有区&#xff0c;段&#xff0c;页&#xff1f; 1)页是内存和磁盘之间交互的基本单位内存中的值修改之后刷到磁盘的时候还是以页为单位的索引结构给程序员提供了高效的索引实现方式&#xff0c;不过索引信息以及数据记录都是记录在文件上面的&#xff0c;确切来说是…

矩阵的c++实现(2)

上一次我们了解了矩阵的运算和如何使用矩阵解决斐波那契数列&#xff0c;这一次我们多看看例题&#xff0c;了解什么情况下用矩阵比较合适。 先看例题 1.洛谷P1939 【模板】矩阵加速&#xff08;数列&#xff09; 模板题应该很简单。 补&#xff1a;1<n<10^9 10^9肯定…

给列起别名(关键字:as)

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: select 列名1 as 别名1, 列名2 as 别名2, 列名n as 别名n from 表名; 说明&#xff1a;可以省略as&#xff0c;列名和别名之间使用空格…

MySQL——使用mysqldump备份与恢复数据

目录 1.mysqldump简介 2.mysqldump备份数据 2.1 备份所有数据库 2.2 备份一个/多个数据库 2.3 备份指定库中的指定表 3.mysqldump恢复数据 3.1 恢复数据库 3.2 恢复数据表 1.mysqldump简介 mysqldump命令可以将数据库中指定或所有的库、表导出为SQL脚本。表的结构和表中…

并网逆变器+VSG控制+预同步控制+电流电流双环控制(Simulink仿真实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

火山引擎 ByteHouse:TB 级数据下,如何实现高效、稳定的数据导入

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 近期&#xff0c;火山引擎开发者社区、火山引擎数智平台&#xff08;VeDI&#xff09;联合举办以《数智化转型背景下的火山引擎大数据技术揭秘》为主题的线下 Meeup…

做好微信CRM,这些功能你不可不知!

在当前的数字化时代&#xff0c;微信已成为我们日常生活中的重要元素&#xff0c;无论是社交交流、信息传递还是商务合作&#xff0c;微信都扮演着不可或缺的角色。为了更有效地管理微信资源并提高工作效率&#xff0c;很多组织和公司都选择引入微信CRM系统。那么&#xff0c;怎…

【算法学习】-【双指针】-【盛水最多的容器】

LeetCode原题链接&#xff1a;盛水最多的容器 下面是题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。…

sheng的学习笔记-【中文】【吴恩达课后测验】Course 1 - 神经网络和深度学习 - 第三周测验

课程1_第3周_测验题 目录&#xff1a;目录 第一题 1.以下哪一项是正确的&#xff1f; A. 【  】 a [ 2 ] ( 12 ) a^{[2](12)} a[2](12)是第12层&#xff0c;第2个训练数据的激活向量。 B. 【  】X是一个矩阵&#xff0c;其中每个列都是一个训练示例。 C. 【  】 a 4 […

如果在 Mac 上的 Safari 浏览器中无法打开网站

使用网络管理员提供的信息更改代理设置。个人建议DNS解析&#xff0c;设置多个例如114.114.114.114 8.8.8.8 8.8.4.4 如果打不开网站&#xff0c;请尝试这些建议。 在 Mac 上的 Safari 浏览器 App 中&#xff0c;检查页面无法打开时出现的信息。 这可能会建议解决问题的…

pandas read_json时ValueError: Expected object or value的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

坦克世界WOT知识图谱三部曲之爬虫篇

文章目录 关于坦克世界1. 爬虫任务2. 获取坦克列表3. 获取坦克具体信息结束语 关于坦克世界 《坦克世界》(World of Tanks, WOT)是我在本科期间玩过的一款战争网游&#xff0c;由Wargaming公司研发。2010年10月30日在俄罗斯首发&#xff0c;2011年4月12日在北美和欧洲推出&…

IDT 一款自动化挖掘未授权访问漏洞的信息收集工具

IDT v1.0 IDT 意为 Interface detection&#xff08;接口探测) 项目地址: https://github.com/cikeroot/IDT/该工具主要的功能是对批量url或者接口进行存活探测&#xff0c;支持浏览器自动打开指定的url&#xff0c;避免手动重复打开网址。只需输入存在批量的url文件即可。 …

Linux删除空目录/非空目录和文件

一、删除目录 删除名为mydir的空目录: rmdir mydir 删除名为mydir的非空目录(非空目录是指该目录包含了其他文件或子目录&#xff0c;而不是空的或没有任何内容的目录) rm -r mydir 删除mydir1下的空目录mydir2 rmdir mydir1/mydir2 删除当前目录下所有以dir一个数字结尾的目录…

Nginx限流熔断

一、Nginx限流熔断 Nginx 是一款流行的反向代理和负载均衡服务器&#xff0c;也可以用于实现服务熔断和限流。通过使用 Nginx 的限流和熔断模块&#xff0c;比如&#xff1a;ngx_http_limit_req_module 和 ngx_http_limit_conn_module&#xff0c;可以在代理层面对服务进行限流…

set和map的封装

目录 介绍 红黑树代码 set insert的迭代器转换问题 为什么会有这样的问题? 如何解决 代码 map 注意点 代码 介绍 set和map的底层都是红黑树,所以我们可以在自己实现的红黑树(简易版)的基础上,进行封装,成为简易的set和map 红黑树代码 #pragma once#include <…

GEE16: 区域日均降水量计算

Precipitation 1. 区域日均降水量计算2. 降水时间序列3. 降水数据年度时间序列对比分析 1. 区域日均降水量计算 今天分析一个计算区域日均降水量的方法&#xff1a; 数据信息&#xff1a;   Climate Hazards Group InfraRed Precipitation with Station data (CHIRPS) is a…

嵌入式软件架构基础设施设计方法

大家好&#xff0c;今天分享一篇嵌入式软件架构设计相关的文章。 软件架构这东西&#xff0c;众说纷纭&#xff0c;各有观点。在我看来&#xff0c;软件架构是软件系统的基本结构&#xff0c;包含其组件、组件之间的关系、组件设计与演进的规则&#xff0c;以及体现这些规则的基…