华为云云耀云服务器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语言实现
二元多项式除法电路原理 例: g ( x ) = x 4 + x 2 + x + 1 g(x)=x^4 + x^2+x+1...
RabbitMQ核心总结
AMQP协议核心概念 RabbitMQ是基于AMQP协议的,通过使用通用协议就可以做到在不同语言之间传递。 server:又称broker,接受客户端连接,实现AMQP实体服务。 connection:连接和具体broker网络连接。 channel:…...
Unicode与UTF-8
软件开发中乱码问题经常遇到,Unicode,UTF-8, ASCII等都是高频词语,不过具体是啥意思其实都不清楚。这个周末研究了一下,略有了解,记录一下。 Unicode Unicode本身是纯理论的东西,和具体计算机实现无关。它…...
A : DS单链表--类实现
Description 用C语言和类实现单链表,含头结点 属性包括:data数据域、next指针域 操作包括:插入、删除、查找 注意:单链表不是数组,所以位置从1开始对应首结点,头结点不放数据 类定义参考 #include<…...
React Hooks —— ref hooks
什么是Hooks Hooks从语法上来说是一些函数。这些函数可以用于在函数组件中引入状态管理和生命周期方法。 React Hooks的优点 简洁 从语法上来说,写的代码少了上手非常简单 基于函数式编程理念,只需要掌握一些JavaScript基础知识与生命周期相关的知识不…...
泛型与Gson解析
/*** 回调接口的一种实现* 用于把网络返回的json字符串转换成参数化类型* 泛型 T 就是用户输入的javaBean的类型*/ public abstract class HttpCallback<T> implements ICallback {Overridepublic void onSuccess (String result) {// result就是网络回来的数据// 把这个…...
c++使用ifstream和ofstream报错:不允许使用不完整的类型
学习《C Primer》关于IO库的部分,输入284页的的代码,出现了报错: 不允许使用不完整的类型 原来的代码: #include <iostream> #include <vector> using namespace std;int main(int argc, char **argv) {ifstream in…...
调试器通用波形显示工具
前言:事情起因是我们实验室买了个无线调试器是CMSIS-DAP的,无法使用J-SCOPE显示波形来方便调PID,所以我就在网上找到了个开源工具链接:http://t.csdnimg.cn/ZqZPY使用方法:工具是好工具,就是没有使用手册&a…...
Linux中getopt函数、optind等变量使用详解
getopt函数、optind等变量使用详解 最近在学习《Unix网络编程》vol2时,发现书中例子经常使用一个命令行解析getopt函数,因为函数声明比较特别,根据自己摸索,遂总结出使用方法。 1. getopt函数的声明 该函数是由Unix标准库提供的…...
RDP协议流程详解(二)Basic Settings Exchange 阶段
RDP连接建立过程,在Connection Initiation后,RDP客户端和服务端将进行双方基础配置信息交换,也就是basic settings exchange阶段。在此阶段,将包含两条消息Client MCS Connect Initial PDU和Server MCS Connect Response PDU&…...
舟山网站建设制作/媒介星软文平台官网
互联网时代带来了数据量的海量增长,数据驱动业务决策成为大势所趋。分析人员迫切的希望能快速的交互式的从海量的数据中抓取有用的信息来辅助决策。在 AWS 上, Amazon S3 对象存储服务由于其高可用性,高持久性,可扩展性和数据格式…...
做伦理电影网站/郑州网络营销推广公司
文|佘凯文 来源|智能相对论(aixdlun) 在经历了起落之后,从2018年下半年开始,智能手表行业进入了稳定增长阶段。 数据显示,2018年全球智能手表出货量达到了4500万台,同比增长56%,而国内市场20…...
如何用网站做淘客/软广告经典例子
JIT是Just-In-Time的缩写,是Java虚拟机把字节码的再编译为本地代码的编译器的名称。我为什么突然想写这个呢?因为国内对JIT的研究非常少,而且面试也问得少,但是我认为JIT对虚拟机特别重要,因为对性能的提升非常明显。j…...
网店的网站设计方案/市场调研公司
前两天儿子生病,带到学校来,用我的机子,结果染了一种很牛的病毒。进程中会出现两个MESS和LSCSSS。用了几种方法也结束不了。后来,试了一下NTSD命令,甚爽。1、利用进程的PID结束进程命令格式:ntsd -c q -p p…...
奉贤青岛网站建设/手机app免费下载
西雅图IT圈:seattleit【今日作者】Dexter读书巨慢理事会会长元宇宙这个概念有多火?除了为了它改名的Meta(虽然大家还是叫它从Facebook),最近又有两大玩家入局。一个是苹果母公司的CEO罗永浩,另一个则是微软…...
免费素材库网站/河北百度推广客服电话
翻译自 Steve Gordon 2020年3月30日的文章 《WHAT ARE .NET WORKER SERVICES?》 随着 .NET Core 3.0 的发布,ASP.NET 团队引入了一个新的 Worker Service 项目模板,该模板作为 .NET SDK 的一部分发布。在本文中,我将向您介绍这个新模板&…...