华为云云耀云服务器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("================> 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的源码;
相关文章:

华为云云耀云服务器L实例评测|基于canal缓存自动更新流程 SpringBoot项目应用案例和源码
前言 最近华为云云耀云服务器L实例上新,也搞了一台来玩,期间遇到各种问题,在解决问题的过程中学到不少和运维相关的知识。 在之前的博客中,介绍过canal的安装和配置,参考博客 拉取创建canal镜像配置相关参数 & …...

vue3 中使用echarts图表——柱状图
柱状图是比较常用的图形结构,所以我先收集一些精美的柱状图 一、柱状图:设置圆角和颜色 <template><div class"box" ref"chartDom"></div> </template> <script setup> import { ref, onMounted } fr…...

基于Java的家政公司服务平台设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...

深入了解 PostgreSQL:功能、特性和部署
PostgreSQL,通常简称为Postgres,是一款强大且开源的关系型数据库管理系统(RDBMS),它在数据存储和处理方面提供了广泛的功能和灵活性。本文将详细介绍 PostgreSQL 的功能、特性以及如何部署和使用它。 什么是 PostgreSQ…...

平台项目列表页实现(二)
这里写目录标题 一、顶部盒子设计1. 顶部盒子包含项目列表和添加项目、退出登录2个按钮 二、项目列表盒子设计三、添加项目盒子设计四、退出登录功能实现五、路由导航守卫实现六、展示项目信息七、bug修复1、当项目名称太长或者项目负责人太长,需要一行展示…...

osg实现鼠标框选
目录 1. 需求的提出 2. 具体实现 2.1. 禁止场景跟随鼠标转动 2.2. 矩形框前置绘制 3. 附加说明 3.1. 颜色设置说明 3.2.矩形框显示和隐藏的另一种实现 1. 需求的提出 有时需要在屏幕通过按住键盘上的某个键如Ctrl键且按住鼠标左键,拖出一个矩形,实现框…...
电路原理解题笔记(一)
文章目录 贼基础的知识等效电阻基尔霍夫电流定律电阻电路的一般分析支路电流法节点电压法回路电流法 电路定理叠加定理戴维宁等效电路诺顿等效电路求某电阻值为多少可吸收最大功率。吸收、释放功率 第一个月,对应猴博士的一到五课时。 贼基础的知识电阻电路的等效变…...

分享几个优秀开源免费管理后台模版,建议收藏!
大家好,我是 jonssonyan 今天和大家分享一些免费开源的后台管理页面,帮助大家快速搭建前端页面。为什么要用模板?道理很简单,原因是方便我们快速开发。我们不应该花太多的时间在页面调整上,而应该把精力放在核心逻辑和…...
BFS模板:844. 走迷宫
给定一个 nmnm 的二维整数数组,用来表示一个迷宫,数组中只包含 00 或 11,其中 00 表示可以走的路,11 表示不可通过的墙壁。 最初,有一个人位于左上角 (1,1)(1,1) 处,已知该人每次可以向上、下、左、右任意…...

re学习(37)DASCTF 2023 0X401七月暑期挑战赛 controflow
程序通过改变栈里面的返回地址来控制程序的控制流 从而达到混淆的效果 左侧有许多被hook的函数 在每个函数开头设置断点 然后观察程序的运行流程 会发现输入的数据会进行 异或 相加 异或 相减 相乘 异或等操作 要注意部分运算的索引是 从[10]开始的 具体思路参考…...

数字IC前端学习笔记:数字乘法器的优化设计(进位保留乘法器)
相关阅读 数字IC前端https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 阵列乘法器设计中限制乘法器速度的是随着数据位宽而迅速增大的串行进位链,如果使用进位保留加法器,则可以避免在设计中引入较长时间的等待&…...
prority_queue的学习
优先级队列(Priority Queue)是一种抽象数据类型,它类似于普通的队列或堆栈,但每个元素都有一个关联的优先级,这个优先级决定了元素在队列中的位置和被访问的顺序。在优先级队列中,具有最高优先级的元素通常…...

【vue3】toRef与toRefs的使用,toRef与ref的区别
假期第四篇,对于基础的知识点,我感觉自己还是很薄弱的。 趁着假期,再去复习一遍 1、toRef与toRefs 创建一个ref对象,其value值指向另一个对象中的某个属性 语法:const name toRef(person,‘name’…...
信息论基础第二章部分习题
2.5 证明若H(Y|X)0,则Y是X的函数 若 H ( Y ∣ X ) 0 H(Y|X) 0 H(Y∣X)0,意味着在已知 X X X 的条件下, Y Y Y 的不确定性为零,即给定 X X X 的值,我们完全确定了 Y Y Y 的值。这表明 Y Y Y 的取值完全由 X X…...
信息化发展73
数字经济 数字经济是继农业经济、工业经济之后的更高级经济形态。从本质上看,数字经济是一种新的技术经济范式,它建立在信息与通信技术的重大突破的基础上,以数字技术与实体经济融合驱动的产业梯次转型和经济创新发展的主引擎,在…...
560. 和为 K 的子数组
题目描述 给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。 示例 1: 输入:nums [1,1,1], k 2 输出:2示例 2: 输入:nums [1,2,3], k 3 输出:2…...

24 mysql all 查询
前言 这里主要是 探究一下 explain $sql 中各个 type 诸如 const, ref, range, index, all 的查询的影响, 以及一个初步的效率的判断 这里会调试源码来看一下 各个类型的查询 需要 lookUp 的记录 以及 相关的差异 此系列文章建议从 mysql const 查询 开始看 测试表结构…...
【Excel单元格数值统计】python实现-附ChatGPT解析
1.题目 Excel单元格数值统计 知识点: 递归、循环数组 时间限制:2s 空间限制:256MB 限定语言:不限 题目描述: Excel工作表中对选定区域的数值进行统计的功能非常实用。仿照Excel的这个功能,请对给定表格中选中区域中的单元格进行求和统计,并输出统计结果。 为简化计算,假设当…...
爬虫项目实战——爬取B站视频
目标:对B站视频详情页url进行视频的爬取。 注:由于B站的音频和视频的链接是分开的,所以在提取是需要分别提取,然后进行合成。 这里只管提取,合成的工作以后再说。 具体步骤 发送请求 对于视频详情页url地址发送请求 …...

关掉在vscode使用copilot时的提示音
1. 按照图示的操作File --> Preferences --> Settings 2. 搜索框输入关键字Sound,因为是要关掉声音,所以找有关声音的设置 3. 找到如下图所示的选项 Audio Cues:Line Has Inline Suggetion,将其设置为Off 这样,就可以关掉suggest code时…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...

未授权访问事件频发,我们应当如何应对?
在当下,数据已成为企业和组织的核心资产,是推动业务发展、决策制定以及创新的关键驱动力。然而,未授权访问这一隐匿的安全威胁,正如同高悬的达摩克利斯之剑,时刻威胁着数据的安全,一旦触发,便可…...