微网站建设渠道,深圳logo设计公司哪家好,公司官网网站搭建,分分作网站前言
最近华为云云耀云服务器L实例上新#xff0c;也搞了一台来玩#xff0c;期间遇到各种问题#xff0c;在解决问题的过程中学到不少和运维相关的知识。
在之前的博客中#xff0c;介绍过canal的安装和配置#xff0c;参考博客
拉取创建canal镜像配置相关参数 …
前言
最近华为云云耀云服务器L实例上新也搞了一台来玩期间遇到各种问题在解决问题的过程中学到不少和运维相关的知识。
在之前的博客中介绍过canal的安装和配置参考博客
拉取创建canal镜像配置相关参数 搭建canal连接MySQL数据库 spring项目应用canal初步
本篇博客给出了canal项目应用的案例详细介绍基于canal实现数据库和缓存同步的流程并给出了核心diamante的源码。 其他相关的华为云云耀云服务器L实例评测文章列表如下 初始化配置SSH连接 安装MySQL的docker镜像 安装redis以及主从搭建 7.2版本redis.conf配置文件 安装Java8环境 配置环境变量 spring项目部署 【!】存在问题未解决 部署spring项目端口开放问题的解决 服务器项目环境搭建MySQLRedisMinio…指南 由于自己原因导致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中的相应数据 哪些数据从缓存中取——不经常更新的数据比如公司的部门仓库等 在项目启动时启动了canalcanal用来监听数据库的变化 业务逻辑前端请求相关数据– 问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: 11111Redis存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) {RedisTemplateString,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 RedisTemplateString,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 % 1000 || emptyCount1){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(ListEntry 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(ListColumn columns) {for (Column column : columns) {System.out.println(column.getName() : column.getValue() update column.getUpdated());}}private void changeAfter(ListColumn 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:设置新的keyListGoodsTypeVo 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里面ListWarehouseVo 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 RedisTemplateString,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商品类型等ListString 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(){MapString,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 ListWarehouseVo findStorehouse() {
// ListStorehouse storehouses storehouseMapper.selectList(null);
// ListWarehouseVo 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){ // 如果有走缓存ListWarehouseVo list (ListWarehouseVo) redisTemplate.opsForValue().get(Constance.STOREHOUSE_REDIS_KEY);log.debug(get storehouseVo from redis: list);return list;}// 如果没有从数据库查询存到redis里面ListWarehouseVo 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代码
templatedivel-rowel-col :span24el-form :inlinetrue label-width80pxel-form-item label领料单号el-input v-modelfindGoodsParams.shoppingNo/el-input/el-form-itemel-form-item label领料数量≥el-input v-modelfindGoodsParams.nums/el-input/el-form-itemel-form-item label原料名称el-input v-modelfindGoodsParams.rawName/el-input/el-form-itemel-form-item label领料仓库el-select v-modelfindGoodsParams.warehouseId placeholder请选择el-option v-foritem in commondata.storehouse :keyitem.value :labelitem.label:valueitem.value/el-option/el-select/el-form-itemel-form-item label状态el-select v-modelfindGoodsParams.status placeholder请选择el-option v-foritem in commondata.status :keyitem.value :labelitem.label:valueitem.value/el-option/el-select/el-form-itemel-form-itemel-button typeprimary clickfindGoods查询/el-buttonel-button typesuccess clickreFindGoods重置/el-button/el-form-item/el-form/el-col/el-rowel-rowel-col :span24el-button typesuccess clickaddGoodsBtn新增/el-buttonel-button typewarning iconel-icon-edit clickbatchDelete批量审批/el-buttonel-button typeprimary iconel-icon-magic-stick clickbatchPickDoing批量领取中/el-buttonel-button typeprimary iconel-icon-shopping-cart-full clickbatchPickDown批量已领完/el-button/el-col/el-rowel-rowel-col :span24el-table :datatableData stylewidth: 100% selection-changehandleSelectionChangeel-table-column typeselection width55/el-table-columnel-table-column proppickNo label领料单号 width240/el-table-columnel-table-column propname label原材料名 width100/el-table-columnel-table-column propnums label领料数量 width80/el-table-columnel-table-column propunit label单位 width80/el-table-columnel-table-column propwarehouse label领料仓库 width180/el-table-columnel-table-column propemp label仓管员 width150/el-table-columnel-table-column propstatus label状态 width80template slot-scopescopespan v-ifscope.row.status 0 stylecolor: red;未审批/spanspan v-ifscope.row.status 1 stylecolor: rgb(9, 209, 109);已审批/spanspan v-ifscope.row.status 2 stylecolor: rgb(44, 39, 205);已领取/spanspan v-ifscope.row.status 3 stylecolor: rgb(173, 16, 157);已领完/span!-- {{ scope.row.status 1 ? 已上架 : 下架 }} --/template/el-table-columnel-table-column label操作template slot-scopescopeel-button typeprimary iconel-icon-edit circleclickloadBtn(scope.row.id)审批/el-button/template/el-table-column/el-table/el-col/el-rowel-rowel-col :span24div classblockel-pagination size-changehandleSizeChange current-changehandleCurrentChange:current-page.synccurrentPage :page-sizes[3, 5, 10] :page-size3layouttotal,sizes, prev, pager, next :totaltotal/el-pagination/div/el-col/el-row!-- 新增原材料入库弹窗 ******* --el-dialog title添加原材料单 :visible.syncbel-formel-rowel-col :span12el-form-item label原料名称 clearableel-select v-modelshoppingNoId placeholder请选择stylewidth: 355px;margin-right:px;margin-left:px changeselectBuyNoel-option v-foritem in shoppingIdToNo :keyitem.label :labelitem.label:valueitem.label/el-option/el-select/el-form-itemel-form-item label数量 :label-widthformLabelWidthel-input v-modelgoods.nums autocompleteoff/el-input/el-form-itemel-form-item label单位 clearableel-select v-modelgoods.unit placeholder请选择商品单位stylewidth: 355px;margin-right:px;margin-left:pxel-option v-foritem in commondata.unit :keyitem.value :labelitem.label:valueitem.value/el-option/el-select/el-form-itemel-form-item label中心仓库 clearableel-select v-modelgoods.warehouse placeholder请选择 disabledstylewidth: 355px;margin-right:px;margin-left:pxel-option v-foritem in commondata.warehouse :keyitem.value :labelitem.label:valueitem.value/el-option/el-select/el-form-itemel-form-item label领料仓库 clearableel-select v-modelgoods.storehouse placeholder请选择stylewidth: 355px;margin-right:px;margin-left:pxel-option v-foritem in commondata.storehouse :keyitem.value :labelitem.label:valueitem.value/el-option/el-select/el-form-item/el-col/el-row/el-formdiv slotfooter classdialog-footerel-button clickb false取 消/el-buttonel-button typeprimary clickaddGoods()确 定/el-button/div/el-dialog/div
/templatescript
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的源码