黑*头条_第2章_文章列表前端成形与后端变身

news2025/1/18 10:47:29

黑*头条_第2章_文章列表前端成形与后端变身

文章目录

  • 黑*头条_第2章_文章列表前端成形与后端变身
  • 文章列表前端成形与后端变身
    • 学习目标
    • 1.前端工程结构
      • 1.1 环境准备
        • 1.1.1 导入工程
        • 1.1.2 测试运行
      • 1.2 weex 跨终端前端框架
      • 1.3 工程结构说明
      • 1.4 源码结构
      • 1.5 WEEX UI
    • 2.文章列表前端开发
      • 2.1 创建文件
      • 2.2 Model定义
        • 2.2.1 在src/pages/home/index.vue中定义列表Model
      • 2.3 实现Api
      • 2.4 实现VIEW
      • 2.5 实现VM
      • 2.6 效果演示
    • 3.Mycat集成
      • 3.1 为什么使用Mycat
      • 3.2 Mycat概念
      • 3.3 数据节点需求
      • 3.4 分库分表设计
      • 3.5 Mycat配置详解
          • server.xml
          • schema.xml
          • rule.xml
          • sequence_db_conf.properties
      • 3.6 Mycat项目集成
    • 4.路由开发
      • 4.1 工程创建
      • 4.2 依赖导入
      • 4.3 算法设计
      • 4.4 算法实现
      • 4.5 算法应用
        • 4.5.1 Burst5050
        • 4.5.2 Burst4040
        • 4.5.3 规则使用
      • 4.6 算法部署
      • 4.7 算法测试
        • 4.7.1 工具测试
        • 4.7.2 BurstUtils类
        • 4.7.3 程序-数据插入
        • 4.7.4 程序-数据查询
        • 4.7.5 程序-数据更新
        • 4.7.6 程序-数据删除
    • 5.后端程序改造
        • 5.1 ap_article
        • 5.2 ap_show_behavior
        • 4.7.4 程序-数据查询
        • 4.7.5 程序-数据更新
        • 4.7.6 程序-数据删除
    • 5.后端程序改造
        • 5.1 ap_article
        • 5.2 ap_show_behavior

文章列表前端成形与后端变身

学习目标

熟悉项目前端工程结构

熟悉Weex的在移动端的好处

熟悉移动端列表页面的结构与开发过程

熟悉生产与测试环境的数据库差异

熟悉Mycat技术的开发与实战技巧

掌握分库分表的设计技巧

掌握Mycat自定义分表算法开发

1.前端工程结构

前端工程基于VUE技术,分为3个独立的项目:
在这里插入图片描述

  • heima-leadnews-app:移动端的功能实现;
  • heima-leadnews-wemedia:自媒体系统的前端实现;
  • heima-leadnews-admin:管理系统的前端实现;

1.1 环境准备

  • Nodejs已安装

    安装资料文件夹下的node即可

  • Cnpm、Nrm、Webpack

    安装完nodejs以后会自动安装npm,但是npm的镜像源是国外的站点,所以建议使用cnpm,或者把npm的镜像源设置为taobao镜像源也是可以的

    需要安装webpack 全局安装

  • Weex Toolkit

    安装weex的toolkit

    npm i -g weex-toolkit 
    weex -v // 查看当前weex工具版本
    
  • WebStorm

1.1.1 导入工程

导入资料文件夹下的原始项目heima-leadnews-app这个客户端项目即可,后续的项目随着课程的深入会依次导入其他项目,在当天资料中解压heima-leadnews-app.zip文件,拷贝到一个没有中文和空格的目录,使用web storm工具打开即可

1.1.2 测试运行

执行命令

在这里插入图片描述

运行效果

在这里插入图片描述

在命令行运行 npm run serve 默认浏览器便会到开对应网页。

也可使用Playground扫描页面上的二维码,在手机上体验网页。

注意:项目默认选用无线网络运行,如需修改,可以修改config/config.js和webpack.dev.conf.js文件中的下列代码:
在这里插入图片描述

注:手机扫描时,手机和电脑应该在同一局域网内。

1.2 weex 跨终端前端框架

https://weex.apache.org/zh/guide/introduction.html

Weex 致力于使开发者能基于通用跨平台的 Web 开发语言和开发经验,来构建 Android、iOS 和 Web 应用。简单来说,在集成了 WeexSDK 之后,你可以使用 JavaScript 语言和前端开发经验来开发移动应用。

1.3 工程结构说明

在这里插入图片描述

1.4 源码结构

在这里插入图片描述

  • apis :按页面存放各个页面的api文件

  • common :存放公共配置和api

  • compoents :存放公用组件

  • langs :存放国际化语言包

  • mock :存放单元测试数据

  • pages :存放页面源码

  • routers :存放路由配置源码

  • stores : 存放本地缓存源码

  • styles :存放样式源码

  • utils :存放通用工具类

1.5 WEEX UI

一个基于 Weex 的富交互、轻量级、高性能的 UI 组件库

官方文档:https://alibaba.github.io/weex-ui/#/cn/

2.文章列表前端开发

为提高开发效率,项目可使用weex-ui快速开发,选用的列表样式参考:

https://alibaba.github.io/weex-ui/#/cn/packages/wxc-tab-page/

2.1 创建文件

创建src/apis/home/api.js文件,用于封装Home页面文章数据请求

function Api(){
    var vue;
}
Api.prototype = {
    setVue : function(vue){
        this.vue = vue;
    },
    // 加载数据
    loaddata : function(params){
        let dir = params.loaddir
        let url = this.getLoadUrl(dir)
        return new Promise((resolve, reject) => {
            this.vue.$request.get(url,params).then((d)=>{
                resolve(d);
            }).catch((e)=>{
                reject(e);
            })
        })
    },
    // 保存展现行为数据
    saveShowBehavior : function(params){
        let ids = [];
        for(let k in params){
            if(params[k]){
                ids.push({id:k});
            }
        }
        console.log(params)
        if(ids.length>0){
            let url = this.vue.$config.urls.get('show_behavior')
            return new Promise((resolve, reject) => {
                this.vue.$request.post(url,JSON.stringify({equipmentId:1,articleIds:ids})).then((d)=>{
                    d.data=ids
                    resolve(d);
                }).catch((e)=>{
                    reject(e);
                })
            })
        }
    },
    // 区别请求那个URL
    getLoadUrl : function(dir){
        let url = this.vue.$config.urls.get('load')
        if(dir==0)
            url = this.vue.$config.urls.get('loadnew')
        else if(dir==2)
            url = this.vue.$config.urls.get('loadmore')
        return url;
    }
}

export default new Api()

创建src/pages/home/config.js文件,用于封装频道Tab页名称和样式

export default {
    tabTitles: [{title: '动态',id:'__dyna__'},
        {title: '推荐',id:'__all__'},
        { title: 'JAVA',id:1},
        { title: 'Python',id:2},
        {title: 'VUE',id:3},
        {title: 'WEEX',id:4},
        {title: '大数据',id:5},
        {title: 'Docker',id:6},
        {title: '其它',id:0}
    ],
    tabStyles: {
        bgColor: '#FFFFFF',
        titleColor: '#9b9b9b',
        activeTitleColor: '#3D3D3D',
        activeBgColor: '#FFFFFF',
        isActiveTitleBold: true,
        iconWidth: 70,
        iconHeight: 70,
        width: 120,
        height: 80,
        fontSize: 24,
        hasActiveBottom: true,
        activeBottomColor: '#3194ff',
        activeBottomHeight: 6,
        activeBottomWidth: 36,
        textPaddingLeft: 10,
        textPaddingRight: 10,
        normalBottomColor: 'rgba(0,0,0,0.4)',
        normalBottomHeight: 2,
        hasRightIcon: false,
        rightOffset: 100
    }
}

2.2 Model定义

2.2.1 在src/pages/home/index.vue中定义列表Model

data: () => ({
    api: null,// API
    shownew: false,//是否显示loadnew动画
    showmore: false,//是否显示loadmore动画
    tabTitles: Config.tabTitles,//频道配置
    tabStyles: Config.tabStyles,//频道样式
    tabList: [],//列表数据集合
    tabPageHeight: 1334,//列表总高度
    params: {
        loaddir: 1,
        index: 0,
        tag: "__all__",
        size: 20,
        max_behot_time: 1559789043,
        min_behot_time: 1559789043
    },//列表数据请求参数
    ashow: {}//列表展示行为记录表
}),

2.3 实现Api

在src/apis/home/api.js中实现Api

首页API需实现load、loadnew、loadmore、show_behavior等后端接口的调用,其实现代码如下

function Api(){
    var vue;
}
Api.prototype = {
    setVue : function(vue){
        this.vue = vue;
    },
    // 加载数据
    loaddata : function(params){
        let dir = params.loaddir
        let url = this.getLoadUrl(dir)
        return new Promise((resolve, reject) => {
            this.vue.$request.get(url,params).then((d)=>{
                resolve(d);
            }).catch((e)=>{
                reject(e);
            })
        })
    },
    // 保存展现行为数据
    saveShowBehavior : function(params){
        let ids = [];
        for(let k in params){
            if(params[k]){
                ids.push({id:k});
            }
        }
        console.log(params)
        if(ids.length>0){
            let url = this.vue.$config.urls.get('show_behavior')
            return new Promise((resolve, reject) => {
                this.vue.$request.post(url,JSON.stringify({equipmentId:1,articleIds:ids})).then((d)=>{
                    d.data=ids
                    resolve(d);
                }).catch((e)=>{
                    reject(e);
                })
            })
        }
    },
    // 区别请求那个URL
    getLoadUrl : function(dir){
        let url = this.vue.$config.urls.get('load')
        if(dir==0)
            url = this.vue.$config.urls.get('loadnew')
        else if(dir==2)
            url = this.vue.$config.urls.get('loadmore')
        return url;
    }
}

export default new Api()

定义通用的url路径处理,common/conf.js中处理url

const  config = {

    urls:{
        baseUrl:'/toutiao',
        load:'api/v1/article/load',
        loadmore:'api/v1/article/loadmore',
        loadnew:'api/v1/article/loadnew',
        // 解决多平台问题
        getBase : function(){
            if(weex.config.env.platform=='Web'){
                return config.urls.baseUrl;
            }else{
                return "http://m.toutiao.com"
            }
        },
        get:function(name){
            let tmp = config.urls[name];
            if(tmp)
                return config.urls.getBase()+"/"+tmp;
            else
                return config.urls.getBase()+"/"+name;
        }
    },style:{
        main_bg:'#3296fa'
    }

}
export default config

2.4 实现VIEW

Index.vue中template部分

VIEW包含头部功能条和文章列表组件,这两个组件采用column布局,其代码如下:

<template>
    <div class="wrapper">
        <div class="top-body">
            <Home_Bar/>
        </div>
        <div class="content-body">
            <wxc-tab-page ref="wxc-tab-page" :tab-titles="tabTitles" :tab-styles="tabStyles" title-type="text"
                          :tab-page-height="tabPageHeight" @wxcTabPageCurrentTabSelected="wxcTabPageCurrentTabSelected">
                <list v-for="(v,index) in tabList" :key="index" class="item-container"
                      :style="{ height: (tabPageHeight - tabStyles.height) + 'px' }">
                    <!-- 下来刷新最新 -->
                    <refresh @refresh='loadnew' :display="shownew?'show':'hide'" class="loading">
                        <loading-indicator class="loading-icon"></loading-indicator>
                        <text class="loading-text">{{load_new_text}}</text>
                    </refresh>
                    <!-- 列表项,并绑定显示事件 -->
                    <cell v-for="(item,key) in v" class="cell" @appear="show(item.id)" :key="key">
                        <wxc-pan-item :ext-id="'1-' + (v) + '-' + (key)" :url="item.id"
                                      @wxcPanItemClicked="wxcPanItemClicked(item.id)" @wxcPanItemPan="wxcPanItemPan">
                            <Item0 v-if="item.type==0" :data="item"/>
                            <Item1 v-if="item.type==1" :data="item"/>
                            <Item3 v-if="item.type==3" :data="item"/>
                        </wxc-pan-item>
                    </cell>
                    <!-- 上来加载更多 -->
                    <loading @loading="load" :display="showmore?'show':'hide'" class="loading">
                        <loading-indicator class="loading-icon"></loading-indicator>
                        <text class="loading-text">{{load_more_text}}</text>
                    </loading>
                </list>
            </wxc-tab-page>
        </div>
    </div>
</template>

样式style

<style lang="less" scoped>
  @import '../../styles/article';
  .wrapper{
    background-color: @body-background;
    font-size: @font-size;
    font-family: @font-family;
    flex-direction : column;
    flex-wrap:wrap;
  }
  .top-body{
    position: fixed;
    left: 0;
    top: 0;
  }
  .content-body{
    flex: 1;
    flex-direction : column;
    margin-top: 100px;
  }
  .item-container {
    width: 750px;
    background-color: #f2f3f4;
  }
  .cell {
    background-color: #ffffff;
  }
</style>

2.5 实现VM

核心方法

// 缓存国际化数据
	computed:{ },
// 初始化变量和页面样式
created () {},
methods: {
  // 列表项在可见区域展示后的事件处理
  show:function(id){ },
  // 上拉加载更多
  loadmore:function(){},
  // 下来刷新数据
  loadnew:function(){},
  // 正常加载数据
  load : function(){},
  // 列表数据转换成View需要的Model对象
  tanfer : function(data){ },
  // 频道页切换事件
  wxcTabPageCurrentTabSelected (e) {},
  // 兼容回调
  wxcPanItemPan (e) {},
  // 列表项点击事件
  wxcPanItemClicked(id){}
}

实现代码

<script>
    import Home_Bar from "@/compoents/bars/home_bar"
    import {WxcTabPage, Utils, BindEnv, WxcPanItem} from 'weex-ui'
    import Item0 from '../../compoents/cells/article_0.vue'
    import Item1 from '../../compoents/cells/article_1.vue'
    import Item3 from '../../compoents/cells/article_3.vue'
    import Config from './config'
    import Api from '@/apis/home/api'

    export default {
        name: 'HeiMa-Home',
        components: {Home_Bar, WxcTabPage, Item0, Item1, Item3, WxcPanItem},
        data: () => ({
            api: null,// API
            shownew: false,//是否显示loadnew动画
            showmore: false,//是否显示loadmore动画
            tabTitles: Config.tabTitles,//频道配置
            tabStyles: Config.tabStyles,//频道样式
            tabList: [],//列表数据集合
            tabPageHeight: 1334,//列表总高度
            params: {
                loaddir: 1,
                index: 0,
                tag: "__all__",
                size: 20,
                max_behot_time: 1559789043,
                min_behot_time: 1559789043
            },//列表数据请求参数
            ashow: {}//列表展示行为记录表
        }),
        computed: {
            // 渲染加载最新和更多的国际化语言
            load_new_text: function () {
                return this.$lang.load_new_text
            },
            load_more_text: function () {
                return this.$lang.load_more_text
            }
        },
        mounted() {
            // 激活推荐按钮
            this.$refs['wxc-tab-page'].setPage(1, null, false);
        },
        created() {
            // 初始化高度,顶部菜单高度120+顶部bar 90
            this.tabPageHeight = Utils.env.getPageHeight() - 210;
            this.tabList = [...Array(this.tabTitles.length).keys()].map(i => []);
            Api.setVue(this);
            let _this = this;
            // 每隔5秒提交一次数据
            setInterval(function () {
                let result = Api.saveShowBehavior(_this.ashow);
                if (result) {
                    result.then((d) => {
                        console.log(d)
                        // 标记已经处理完成
                        let ids = d.data;
                        for (let i = 0; i < ids.length; i++) {
                            _this.ashow[ids[i].id] = false;
                        }
                    });
                }
            }, 5000);
        },
        methods: {
            // 列表项在可见区域展示后的事件处理
            show: function (id) {
                if (this.ashow[id] == undefined) {
                    this.ashow[id] = true;
                }
            },
            // 上拉加载更多
            loadmore: function () {
                this.showmore = true;
                this.params.loaddir = 2
                this.load();
            },
            // 下来刷新数据
            loadnew: function () {
                this.shownew = true;
                this.params.loaddir = 0
                this.load();
            },
            // 正常加载数据
            load: function () {
                Api.loaddata(this.params).then((d) => {
                    this.tanfer(d.data);
                }).catch((e) => {
                    console.log(e)
                })
            },
            // 列表数据转换成View需要的Model对象
            tanfer: function (data) {
                let arr = []
                for (let i = 0; i < data.length; i++) {
                    let tmp = {
                        id: data[i].id,
                        title: data[i].title,
                        comment: data[i].comment,
                        source: data[i].authorName,
                        date: data[i].publishTime,
                        type: data[i].layout,
                        image: data[i].images == null ? [] : data[i].images.split(','),
                        icon: '\uf06d'
                    }
                    let time = data[i].publishTime;
                    if (this.params.max_behot_time < time) {
                        this.params.max_behot_time = time;
                    }
                    if (this.params.min_behot_time > time) {
                        this.params.min_behot_time = time;
                    }
                    arr.push(tmp);
                }
                let newList = [...Array(this.tabTitles.length).keys()].map(i => []);
                if (this.params.loaddir == 0) {
                    arr = this.tabList[this.params.index].concat(arr);
                } else {
                    arr = arr.concat(this.tabList[this.params.index]);
                }
                newList[this.params.index] = arr;
                this.tabList = newList;
                this.showmore = false;
                this.shownew = false;
            },
            // 频道页切换事件
            wxcTabPageCurrentTabSelected(e) {
                this.params.loaddir = 1
                this.params.index = e.page
                this.params.tag = Config.tabTitles[e.page]['id'];
                this.load();
            },
            // 兼容回调
            wxcPanItemPan(e) {
                if (BindEnv.supportsEBForAndroid()) {
                    this.$refs['wxc-tab-page'].bindExp(e.element);
                }
            },
            // 列表项点击事件
            wxcPanItemClicked(id) {
            }
        }
    };
</script>

完整的代码:

<template>
  <div class="wrapper">
    <div class="top-body"><Home_Bar/></div>
    <div class="content-body">
      <wxc-tab-page ref="wxc-tab-page" :tab-titles="tabTitles" :tab-styles="tabStyles" title-type="text" :tab-page-height="tabPageHeight" @wxcTabPageCurrentTabSelected="wxcTabPageCurrentTabSelected">
        <list v-for="(v,index) in tabList"  :key="index" class="item-container" :style="{ height: (tabPageHeight - tabStyles.height) + 'px' }">
          <!-- 下来刷新最新 -->
          <refresh @refresh='loadnew'  :display="shownew?'show':'hide'" class="loading">
            <loading-indicator class="loading-icon"></loading-indicator>
            <text class="loading-text">{{load_new_text}}</text>
          </refresh>
          <!-- 列表项,并绑定显示事件 -->
          <cell v-for="(item,key) in v" class="cell" @appear="show(item.id)" :key="key">
            <wxc-pan-item :ext-id="'1-' + (v) + '-' + (key)" @wxcPanItemClicked="wxcPanItemClicked(item)" @wxcPanItemPan="wxcPanItemPan">
              <Item0 v-if="item.type==0" :data="item"/>
              <Item1 v-if="item.type==1" :data="item"/>
                <Item3 v-if="item.type==2" :data="item"/>
              <Item3 v-if="item.type==3" :data="item"/>
            </wxc-pan-item>
          </cell>
          <!-- 上来加载更多 -->
          <loading @loading="loadmore" :display="showmore?'show':'hide'" class="loading">
            <loading-indicator class="loading-icon"></loading-indicator>
            <text class="loading-text">{{load_more_text}}</text>
          </loading>
        </list>
        <text slot="rightIcon">1212</text>
      </wxc-tab-page>
    </div>
  </div>
</template>

<script>
  import Home_Bar from "@/compoents/bars/home_bar"
  import WxcTabPage from "@/compoents/tabs/home_tabs"
  import {Utils, BindEnv,WxcPanItem } from 'weex-ui'
  import Item0 from '../../compoents/cells/article_0.vue'
  import Item1 from '../../compoents/cells/article_1.vue'
  import Item3 from '../../compoents/cells/article_3.vue'
  import Config from './config'
  import Api from '@/apis/home/api'

  const modal = weex.requireModule("modal")

  export default {
    name: 'HeiMa-Home',
    components: {Home_Bar,WxcTabPage, Item0,Item1,Item3,WxcPanItem},
    data: () => ({
      api:null,// API
      shownew:true,//是否显示loadnew动画
      showmore:false,//是否显示loadmore动画
      tabTitles: Config.tabTitles,//频道配置
      tabStyles: Config.tabStyles,//频道样式
      tabList: [...Array(Config.tabTitles.length).keys()].map(i => []),//列表数据集合
      tabPageHeight: 1334,//列表总高度
      params:{
        loaddir:1,
        index:0,
        tag:"__all__",
        size:10,
        max_behot_time:0,
        min_behot_time:20000000000000
      },//列表数据请求参数
      ashow : {},//列表展示行为记录表
      timer : null//定时函数
    }),
    computed:{
      // 渲染加载最新和更多的国际化语言
      load_new_text:function(){return this.$lang.load_new_text},
      load_more_text:function(){return this.$lang.load_more_text}
    },
    mounted(){
      // 激活推荐按钮
      this.$refs['wxc-tab-page'].setPage(1,null,true);
    },
    destroyed(){
      clearInterval(this.timer)
    },
    created () {
      // 初始化高度,顶部菜单高度120+顶部bar 90
      this.tabPageHeight = Utils.env.getPageHeight()-222;
      Api.setVue(this);
      let _this = this;
      // 每隔5秒提交一次数据
      this.timer = setInterval(function(){
        let result = Api.saveShowBehavior(_this.ashow);
        if(result){
          result.then((d)=>{
            // 标记已经处理完成
            let ids=d.data;
            for(let i=0;i<ids.length;i++){
              _this.ashow[ids[i].id]=false;
            }
          });
        }
      },5000);
    },
    methods: {
      // 列表项在可见区域展示后的事件处理
      show:function(id){
        if(this.ashow[id]==undefined){
          this.ashow[id]=true;
        }
      },
      // 上拉加载更多
      loadmore:function(){
        this.showmore=true;
        this.params.loaddir=2
        this.load();
      },
      // 下来刷新数据
      loadnew:function(){
        this.shownew=true;
        this.params.loaddir=0
        this.load();
      },
      // 正常加载数据
      load : function(){
        Api.loaddata(this.params).then((d)=>{
          this.tanfer(d.data);
        }).catch((e)=>{
          console.log(e)
        })
      },
      // 列表数据转换成View需要的Model对象
      tanfer : function(data){
        if(data.length==0){
          this.showmore=false;
          this.shownew=false;
          modal.toast({message:'没有数据了...',duration:3})
          return ;
        }
        let arr = []
        for(let i=0;i<data.length;i++){
          let ims = []
          if(data[i].images){
            ims = data[i].images.replace(/[\[\]]/ig,'').split(',')
          }
          let tmp = {
            id:data[i].id,
            title:data[i].title,
            comment:data[i].comment,
            authorId:data[i].author_id,
            source:data[i].author_name,
            date:data[i].publish_time,
            type:ims.length==2?1:ims.length,
            image:ims,
            icon:'\uf06d'
          }
          let time = data[i].publish_time;
          if(this.params.max_behot_time<time){
            this.params.max_behot_time=time;
          }
          if(this.params.min_behot_time>time){
            this.params.min_behot_time=time;
          }
          arr.push(tmp);
        }
        let newList = [...Array(this.tabTitles.length).keys()].map(i => []);
        if(this.params.loaddir!=0){
          arr = this.tabList[this.params.index].concat(arr);
        }else{
          arr=arr.concat(this.tabList[this.params.index]);
        }
        newList[this.params.index] = arr;
        this.tabList = newList;
        this.showmore=false;
        this.shownew=false;
      },
      // 频道页切换事件
      wxcTabPageCurrentTabSelected (e) {
        this.params.loaddir=1
        this.params.index=e.page
        this.params.tag = Config.tabTitles[e.page]['id'];
        this.params.max_behot_time=0
        this.params.min_behot_time=20000000000000
        this.shownew=true
        this.load();
      },
      // 兼容回调
      wxcPanItemPan (e) {
        if (BindEnv.supportsEBForAndroid()) {
          this.$refs['wxc-tab-page'].bindExp(e.element);
        }
      },
      // 列表项点击事件
      wxcPanItemClicked(item){
        this.$router.push({
          name:'article-info',
          params:item
        })
      }
    }
  };
</script>

<style lang="less" scoped>
  @import '../../styles/article';
  .wrapper{
    background-color: @body-background;
    font-size: @font-size;
    font-family: @font-family;
    flex-direction : column;
    flex-wrap:wrap;
  }
  .top-body{
    position: fixed;
    left: 0;
    top: 0;
  }
  .content-body{
    flex: 1;
    flex-direction : column;
    margin-top: 90px;
  }
  .item-container {
    width: 750px;
    background-color: #ffffff;
  }
  .cell {
    background-color: #ffffff;
  }
</style>

2.6 效果演示

在这里插入图片描述

3.Mycat集成

3.1 为什么使用Mycat

我们的项目设计目标如下图:
在这里插入图片描述

从中可以看出,有上亿级别的用户,单表存储已超出Mysql最优性能数据量区间,因此会导致数据库各种操作效率会非常低下,此时可以通过分库分表的方案来解决。Mycat是一款非常优秀的分布式数据库中间件。对读写分离和分库分表都有支持,而且比较易用,对原有的应用系统侵入比较小,系统改造比较易于实现。黑马头条项目将以此作为分库分表的中间拆分数据表,以实现设计目标。

3.2 Mycat概念

mycat相关介绍,请参考资料文件夹/Mycat权威指南.pdf

数据节点: DataNode:存储数据的节点,每个节点可以分配一个或多个数据表。
主机节点:Datahost:主机节点,每个主机可以分配一个或多个数据节点。
分表策略:根据分表策略决定数据写入到哪个节点。
管理信息:管理MYCAT连接和配置信息。

3.3 数据节点需求

按照黑马头条项目数据量需求,如何设计分库分表可参考以下示例:

  • 用户数据表 ap_user
  • 数据量1亿条
  • 每日新增用户100万
  • 一年大概会产生4亿条数据

一般情况下,一个优化后的MySQL单表可以存储1000万条数据。也是我们分表的基准。所以一年后约为4亿条数据,根据我们单表的1000万基准,划分40个节点。

以此类推,可规划出项目中每个数据库的DN节点数需求如下:

  • app_info,需40个
  • app_behavior,需50个
  • wemedia,需6个
  • crawlers,需6个
  • admin,需4个

3.4 分库分表设计

除了节点需求之外,还需要设计每张表的分表数量,计算的方法类似节点的计算,但存在一些细节地方,详见以下说明:

  • 需要考虑表是否扩容,数据量存表之后,如何扩容?
  • 需要考虑分表的关键字段,是一个字段分表,还是复合字段分表?
  • 需要考虑主键生成方式,托管方式还是编程式?
  • 需要考虑分表数量
  • 需要考虑分表存放的DN位置,如何均匀的分配节点上表数据存储,已达数据均匀的目的?
  • 需要考虑是否进行读写分离(本例中不涉及,可线下探讨拓展)
  • 需要考虑是否进行热备数据源(本例中不涉及,可线下探讨拓展)

如app_info分库分表设计输出的信息如下(其它库表详见资料下《数据库路由设计.xlsx》):

在这里插入图片描述

3.5 Mycat配置详解

MYCAT常用配置文件为一下4个:

server.xml
  • 配置mycat连接信息
  • 一些性能优化等管理信息

这里我们主要配置了连接信息:

<user name="root">
    <property name="password">123456</property>
    <property name="schemas">itheima-news</property>
</user>
schema.xml
  • 配置Mycat节点信息
  • 配置Mycat主机信息
  • 配置分表策略
  • 一些连接信息或者读写分离主机配置

定义数据节点:

<dataNode name="DNAP_0" dataHost="HOST0" database="app_info_0" />
  • name:数据节点名称
  • dataHost:主机名称 跟dataHost name="HOST0"标签 name值对应
  • database:节点的数据库名

定义主机节点

<dataHost name="HOST0" maxCon="600" minCon="200" balance="0"
	                  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
	        <heartbeat>select user()</heartbeat>
	        <writeHost host="HOST0_M" url="47.94.7.85:3306" user="root" password="root"/>
	</dataHost>
  • name:主机节点名称
  • minCon:最小连接线程
  • maxCon:最大连接线程
  • balance:负载均衡策略
  • writeHost 该标签配置节点对应的mysql主机

定义表信息

<table name="ap_article" dataNode="DNAP_$0-39" autoIncrement="true" primaryKey="id" rule="mod-long40"/>
  • name:数据库表名
  • dataNode:该表在那些数据节点设置了分片
  • autoIncrement:是否自增,如果设置为true,需要在sequence_db_conf.properties文件配置自增相关数据。
  • rule:分片策略 mod-long40表示 主键求余40
rule.xml

该文件主要定义分表策略:
示例:
主键求余策略:

  • 配置 function class指定分片算法 本例为求余算法
  • 配置 tableRule 设置对那个字段执行分片操作 本例为id对40求余
	<function name="mod-long40" class="io.mycat.route.function.PartitionByMod">
		<!-- how many data nodes -->
		<property name="count">40</property>
	</function>
	
	<tableRule name="mod-long40">
		<rule>
			<columns>id</columns>
			<algorithm>mod-long40</algorithm>
		</rule>
	</tableRule>
sequence_db_conf.properties

该文件为全局自增主键配置对于table定义了autoIncrement=“true” primaryKey="id"的表,需要配置全局自增序号(注意全大写)。

AP_ARTICLE=DNSQ

同时在DNSQ也就是app_seq表里插入一条记录:

INSERT INTO `app_seq`.`MYCAT_SEQUENCE` (`name`, `current_value`, `increment`) VALUES ('ap_article', '1000', '1000');
  • current_value:为初始值
  • increment:为每次获取序列的自增值,该值的数据根据对应表的数据新增速率设置。

3.6 Mycat项目集成

mycat的连接使用跟MySQL类似:只需要配置文件配置即可

# 数据库配置
mysql.core.jdbc.url=jdbc:mysql://localhost:8066/itheima-news?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
mysql.core.jdbc.username=root
mysql.core.jdbc.password=654321

注意:这里密码做了反转处理,具体的情参考项目配置。本文使用的配置文件的全部内容在本文档所在文件下对应的名称。

4.路由开发

为便于管理维护Mycat的路由配置和自定分片算法的源码,在工程下创建service-mycat模块。

4.1 工程创建

  • 在根项目下创建Maven module项目,项目名称为service-mycat,groupId:com.heima。创建后在根pom.xml中module元素中增加以下代码:
<module>service-mycat</module>
  • 在service-mycat pom.xml中增加父项目信息:
<parent>
  <artifactId>heima-leadnews</artifactId>
  <groupId>com.heima</groupId>
  <version>1.0-SNAPSHOT</version>
</parent>
  • 在service-mycat路径下拷贝dev、test、prod三个环境配置文件

  • service-mycat根路径下创建config文件夹(用于存放mycat的路由配置,便于持续部署),并拷贝‘资料/ mycat初始配置文件’下文件到config下

4.2 依赖导入

开发Mycat的分片策略,需要依赖Mycat-server-1.6.7.1-release.jar文件。在项目中以本地文件方式进行导入,操作步骤如下:

  • 在service-mycat下新建文件夹libs

  • 拷贝讲义资料下面的Mycat-server-1.6.7.1-release.jar到libs中

  • 在service-mycat/pom.xml中做以下依赖配置

<dependencies>
      <dependency>
          <groupId>com.mycat</groupId>
          <artifactId>mycat-server</artifactId>
          <version>1.0.0</version>
          <scope>system</scope>
          <systemPath>${basedir}/libs/Mycat-server-1.6-release.jar</systemPath>
      </dependency>
</dependencies>

4.3 算法设计

在项目中分片字段规范命名为burst,类型为String,值格式为dataId-分表ID,dataId用于分组ID计算,为数据量提供扩展性;分表ID用于组内表的分配,其分片公式如下:

分片ID = *(dataId/volume) step +分表ID/mod **

  • Volume是每组分片的数据容量

  • Step是每组分片的DateNode数量

  • Mode是表在每组分片中的节点数量

4.4 算法实现

实现步骤如下:

  • 创建类文件:com.heima.HeiMaBurstRuleAlgorithm

  • 修改类继承AbstractPartitionAlgorithm 和实现RuleAlgorithm接口

  • 在类中定义volume、step、mod三个变量,并提供set方法

  • 实现calculate 等值分片DN计算方法(参数是burst值)

  • 实现calculateRange范围分片DN计算方法(参数是burst值)

完整代码如下:

/**
 * 自定义多字段算法计算
 */
public class HeiMaBurstRuleAlgorithm extends AbstractPartitionAlgorithm implements RuleAlgorithm {
    // 单组数据容量
    Long volume;
    // 单组DN节点数量
    Integer step;
    // 分片模
    Integer mod;

    public void init(){}

    /**
     *
     * @param columnValue 数据ID-桶ID
     * @return
     */
    public Integer calculate(String columnValue){
        if(columnValue!=null){
            String[] temp = columnValue.split("-");
            if(temp.length==2){
                try {
                    Long dataId = Long.valueOf(temp[0]);
                    Long burstId = Long.valueOf(temp[1]);
                    int group = (int)(dataId/volume)*step;
                    int pos = group + (int)(burstId%mod);
                    System.out.println("HEIMA RULE INFO ["+columnValue+"]-[{"+pos+"}]");
                    return pos;
                }catch (Exception e){
                    System.out.println("HEIMA RULE INFO ["+columnValue+"]-[{"+e.getMessage()+"}]");
                }
            }
        }
        return 0;
    }

    /**
     * 范围计算
     * @param beginValue
     * @param endValue
     * @return
     */
    public Integer[] calculateRange(String beginValue, String endValue){
        if(beginValue!=null&&endValue!=null){
            Integer begin = calculate(beginValue);
            Integer end = calculate(endValue);
            if(begin == null || end == null){
                return new Integer[0];
            }
            if (end >= begin) {
                int len = end - begin + 1;
                Integer[] re = new Integer[len];
                for (int i = 0; i < len; i++) {
                    re[i] = begin + i;
                }
                return re;
            }
        }
        return new Integer[0];
    }

    public void setVolume(Long volume) {
        this.volume = volume;
    }

    public void setStep(Integer step) {
        this.step = step;
    }

    public void setMod(Integer mod) {
        this.mod = mod;
    }

}

4.5 算法应用

在项目中主要用到50DN、40DN的可扩展分片算法,可定义成两个tableRule.

4.5.1 Burst5050

Burst5050单组有50个DN,单组容量为5亿,分50张表,每个DN上存储一张对应表;其定义在service-mycat/config/rule.xml中,定义代码为:

<tableRule name="burst5050">
   <rule>
      <columns>burst</columns>
      <algorithm>burst5050</algorithm>
   </rule>
</tableRule>
 
<function name="burst5050" class="com.heima.HeiMaBurstRuleAlgorithm">
   <property name="volume">500000000</property><!-- 单组容量 -->
   <property name="step">50</property><!-- 单组节点量 -->
   <property name="mod">50</property><!-- 单组数据mod -->
</function>

4.5.2 Burst4040

Burst4040单组有40个DN,单组容量为4亿,分40张表,每个DN上存储一张对应表;其定义在service-mycat/config/rule.xml中,定义代码为:

<tableRule name="burst4040">
   <rule>
      <columns>burst</columns>
      <algorithm>burst4040</algorithm>
   </rule>
</tableRule>
 
<function name="burst5050" class="com.heima.HeiMaBurstRuleAlgorithm">
   <property name="volume">400000000</property><!-- 单组容量 -->
   <property name="step">40</property><!-- 单组节点量 -->
   <property name="mod">40</property><!-- 单组数据mod -->
</function>

4.5.3 规则使用

在service-mycat/config/schema.xml中,可使用以上定义的规则,示例如下:

<table name="ap_behavior_entry" dataNode="DNBE_$0-49" rule="burst5050"/>
<table name="ap_collection" dataNode="DNBE_$0-40" rule="burst4040"/>

在service-mycat/config/sequence_db_conf.properties中去掉对应表的sequence配置:

 AP_BEHAVIOR_ENTRY=...
 AP_COLLECTION=...

4.6 算法部署

  • 进入到项目service-mycat项目根路径,运行mvn clean package打包命令打包项目

  • 拷贝service-mycat/config下的文件到mycat安装目录下的config文件夹下

  • 拷贝service-mycat/config下的文件到mycat安装目录下的config文件夹下

  • 拷贝service-mycat/target/ service-mycat-1.0-SNAPSHOT.jar文件到mycat安装目录lib文件夹下

  • 重启mycat服务

4.7 算法测试

算法测试分为工具测试和程序应用测试,工具测试用于开发过程中辅助开发人员,程序应用测试在此会说明项目中用到的方式,具体测试在后面的功能中进行演示。

注意:以下测试说明只针对带有burst复合字段分库分表的物理表。

4.7.1 工具测试

在mysql客户端工具中我们可以通过explain关键字查看sql会在哪些DN节点上执行。例:
在这里插入图片描述

4.7.2 BurstUtils类

此工具类用于拼接处理burst,定义在utils工程下com.heima.common.BurstUtils,其实现代码如下:

/**
 * 分片桶字段算法
 */
public class BurstUtils {

    public final static String SPLIT_CHAR = "-";

    /**
     * 用-符号链接
     * @param fileds
     * @return
     */
    public static String encrypt(Object... fileds){
        StringBuffer sb  = new StringBuffer();
        if(fileds!=null&&fileds.length>0) {
            sb.append(fileds[0]);
            for (int i = 1; i < fileds.length; i++) {
                sb.append(SPLIT_CHAR).append(fileds[i]);
            }
        }
        return sb.toString();
    }

    /**
     * 默认第一组
     * @param fileds
     * @return
     */
    public static String groudOne(Object... fileds){
        StringBuffer sb  = new StringBuffer();
        if(fileds!=null&&fileds.length>0) {
            sb.append("0");
            for (int i = 0; i < fileds.length; i++) {
                sb.append(SPLIT_CHAR).append(fileds[i]);
            }
        }
        return sb.toString();
    }
}

4.7.3 程序-数据插入

在程序中要正确的插入数据,需要给对应表增加burst字段,并在程序中拼接字段值,同时数据主键id的值需要在程序中生成(既项目中使用ZKSEQUENCE方式生成),例:

程序中拼接字段值:

alb.setId(sequences.sequenceApLikes());//设置主键
alb.setBurst(BurstUtils.encrypt(alb.getId(),alb.getBehaviorEntryId()));//设置分片

Mapper.xml中正常插入:

<insert id="insert" parameterType="com.heima.article.mysql.core.model.pojos.app.ApLikesBehavior" >
  insert into ap_likes_behavior (id, behavior_entry_id, entry_id, type, operation, created_time,burst)
  values (#{id}, #{behaviorEntryId}, #{entryId}, #{type}, #{operation},
    #{createdTime},#{burst})
</insert>

4.7.4 程序-数据查询

  • 【问题】

数据查询的问题有两点:

1、不能明确数据id,所以无法直接拼接burst

2、 burst字段用于分片,写在where条件之后不够优雅

  • 【解决方案】

第一个问题查询时可假定id为0,既默认为在第一组中查询,以后有多组时可以默认每组第一个数据id;这样设计的原因是只要组容量内的id和第一个数据id计算出的结果一致。

第二个问题可以通过Mycat提供的注解语法优雅的编写我们的SQL。

  • 【示例】

综合以上解决思路,代码实现的示例如下:

<select id="selectLastLike" resultMap="BaseResultMap">
  /*!mycat:sql=select id from ap_likes_behavior where burst='${burst}'*/
    select * from ap_likes_behavior where behavior_entry_id=#{objectId} and entry_id=#{entryId} and type=#{type} order by created_time desc limit 1
</select>

以上代码在Mycat中执行,首先会执行注解代码确定SQL路由的指定节点,然后再分发SQL具体执行。其中注解中的burst有多个可以使用in,其值需用$连接(由于burst字段完全有后端程序控制值,所以这里不存在外部安全问题),否则会抛出预编译参数不匹配的问题。

4.7.5 程序-数据更新

数据更新请参考《数据查询》

4.7.6 程序-数据删除

数据删除请参考《数据查询》

5.后端程序改造

5.1 ap_article

已实现的文章列表查询功能,分了40个表,对于查询功能暂无需调整。

5.2 ap_show_behavior

ap_show_behavior数据是通过批量方式插入的,集成Mycat之后,需要使用注解,以便实现注解的托管生成。

<insert id="saveBehaviors">
  /*!mycat:catlet=io.mycat.route.sequence.BatchInsertSequence */
  insert into ap_show_behavior ( entry_id, article_id,is_click, show_time, created_time) values  
    <foreach item="item" collection="articleIds" separator=",">    
        (#{entryId}, #{item},0, now(),now())  
    </foreach>
</insert>
ation},
    #{createdTime},#{burst})
</insert>

4.7.4 程序-数据查询

  • 【问题】

数据查询的问题有两点:

1、不能明确数据id,所以无法直接拼接burst

2、 burst字段用于分片,写在where条件之后不够优雅

  • 【解决方案】

第一个问题查询时可假定id为0,既默认为在第一组中查询,以后有多组时可以默认每组第一个数据id;这样设计的原因是只要组容量内的id和第一个数据id计算出的结果一致。

第二个问题可以通过Mycat提供的注解语法优雅的编写我们的SQL。

  • 【示例】

综合以上解决思路,代码实现的示例如下:

<select id="selectLastLike" resultMap="BaseResultMap">
  /*!mycat:sql=select id from ap_likes_behavior where burst='${burst}'*/
    select * from ap_likes_behavior where behavior_entry_id=#{objectId} and entry_id=#{entryId} and type=#{type} order by created_time desc limit 1
</select>

以上代码在Mycat中执行,首先会执行注解代码确定SQL路由的指定节点,然后再分发SQL具体执行。其中注解中的burst有多个可以使用in,其值需用$连接(由于burst字段完全有后端程序控制值,所以这里不存在外部安全问题),否则会抛出预编译参数不匹配的问题。

4.7.5 程序-数据更新

数据更新请参考《数据查询》

4.7.6 程序-数据删除

数据删除请参考《数据查询》

5.后端程序改造

5.1 ap_article

已实现的文章列表查询功能,分了40个表,对于查询功能暂无需调整。

5.2 ap_show_behavior

ap_show_behavior数据是通过批量方式插入的,集成Mycat之后,需要使用注解,以便实现注解的托管生成。

<insert id="saveBehaviors">
  /*!mycat:catlet=io.mycat.route.sequence.BatchInsertSequence */
  insert into ap_show_behavior ( entry_id, article_id,is_click, show_time, created_time) values  
    <foreach item="item" collection="articleIds" separator=",">    
        (#{entryId}, #{item},0, now(),now())  
    </foreach>
</insert>

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

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

相关文章

算法实验题(涉外黄成老师!!!)

日期 2022.11.19 目录 实验报告一 第一题 2 实验报告二 第二题 3 实验报告三 第三题 4 实验报告四 第四题 5 实验报告五 第五题 6 实验报告六 第六题 7 实验报告一 第一题 一、实验目的 由1&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;7&#xff0c;8这六个数字所组…

剑指 Offer II 021. 删除链表的倒数第 n 个结点【链表】

难度等级&#xff1a;中等 上一篇算法&#xff1a; 82. 删除排序链表中的重复元素 II【链表】 力扣此题地址&#xff1a; 剑指 Offer II 021. 删除链表的倒数第 n 个结点 - 力扣&#xff08;LeetCode&#xff09; 1.题目&#xff1a;删除链表的倒数第 n 个结点 给定一个链表&a…

DWGViewX Pro 2021.4.X Crack by Cracki

DWGViewX pro 2021.4.X --Ω578867473 DWGViewX 是一个 ActiveX 组件&#xff0c;可让您在一个查看器中管理和查看 DWG、DXF 和 DWF 工程图。查看 R14 到 2022 版本的 DWG、DXF 和 DWF。加载本地磁盘或网络网站上的图纸&#xff0c;并使用查看器缩放、平移、旋转图纸、打开/关闭…

Java中的线程

线程 什么是线程&#xff1a; 什么是多线程&#xff1a; 学习目的&#xff1a; 多线程的创建 方式一&#xff1a;继承Thread类 public class MyThread{public static void main(String[] args) {Thread thread01 new Thread01();thread01.start();for (int i 0; i < 5; …

翻倍增长!C-V2X商业化“提速”,新一代模组加速“助跑”

C-V2X正在逐步走向商业的规模化部署&#xff0c;由此也带动了C-V2X模组需求的高速增长。 高工智能汽车研究院监测数据显示&#xff0c;今年1-9月中国市场&#xff08;不含进出口&#xff09;乘用车前装标配搭载V2X技术新车交付上险为10.58万辆&#xff0c;同比增长283.33%&…

计算机视觉|投影与三维视觉

这一篇将学习投影与三维视觉&#xff0c;沿用上一篇 计算机视觉|针孔成像&#xff0c;相机内外参及相机标定&#xff0c;矫正的重要性 摄像机内参数矩阵M、畸变参数、旋转矩阵R、平移向量T以及但影响矩阵H。回顾放射和投影变换&#xff0c;并使用POSIT算法从一幅图像中查找获得…

基于stm32单片机有害气体监测检测Proteus仿真

资料编号&#xff1a;097 下面是相关功能视频演示&#xff1a; 97-基于stm32单片机有害气体监测检测Proteus仿真&#xff08;仿真源码全套资料&#xff09;功能介绍&#xff1a;检测当前的有害气体浓度&#xff0c;LCD1602显示&#xff0c;并且可以自动打开关闭风扇&#xff…

Pulsar 各个Shedder分析及新的Shedder -- AvgShedder

看到今年Pulsar 峰会上挺多人分享负载均衡的内容&#xff0c;这里也整理分享一下相关的内容。 社区现有策略的分析 LoadSheddingStrategy pulsar进行shedding的时候&#xff0c;使用的是ThresholdShedder类&#xff0c;ThresholdShedder类是LoadSheddingStrategy接口的其中一…

锐捷SuperVlan实验配置

Super Vlan配置 创建Vlan vlan range 2,3,4,10,20 配置Vlan10为Super Vlan&#xff0c;Vlan 2,3,4为Sub Vlan vlan 10 supervlan subvlan 2,3,4 配置Sub Vlan的地址范围&#xff08;也可以不配置&#xff09; Vlan 2 subvlan-address-range 192.168.10.10 192.168.10.50 配置S…

【数据结构】—时间复杂度or空间复杂度以及基础题目练习

小菜坤日常上传gitee代码&#xff1a;https://gitee.com/qi-dunyan ❤❤❤ 个人简介&#xff1a;双一流非科班的一名小白&#xff0c;期待与各位大佬一起努力&#xff01; 推荐网站&#xff1a;cplusplus.com 目录前言算法与复杂度时间复杂度大O的渐进表示法时间复杂度计算练习…

[附源码]java毕业设计社区疫情防控管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

MySQL纯代码复习(上)

前言 本文章是用于总结尚硅谷MySQL教学视频的记录文章&#xff0c;主要用于复习&#xff0c;非商用 原视频连接&#xff1a;https://www.bilibili.com/video/BV1iq4y1u7vj/?p21&spm_id_frompageDriver&vd_sourcec4ecde834521bad789baa9ee29af1f6c https://www.bilib…

【设计模式】 - 创建者模式 -建造者模式

1. 建造者模式 1.1 概述 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于&#xff1a;某个对象的构建过程复杂的情况。 由于实现了…

小目标检测:基于切图检测的yolov5小目标检测

目前在目标检测方面有着众多的检测框架,比如两阶段的FasterRcnn、以及yolo系列的众多模型。yolo系列在实际中用的最多,一方面性能确实不错,另一方面具有着较多的改进型系列。今天我们主要使用的yolov5系列。具体原理过程就不多说了,大家自行百度。放一张v5的网络结构图。 大…

计算机网络部分(一)

1 请描述 TCP/IP 协议中主机与主机之间通信的三要素 答&#xff1a; IP 地址&#xff08;IP address&#xff09; 子网掩码&#xff08;subnet mask&#xff09; IP 路由&#xff08;IP router&#xff09; 扩展&#xff1a; TCP/IP定义&#xff1a;TCP/IP是基于TCP和IP这两个…

883. 三维形体投影面积

883. 三维形体投影面积 在 n x n 的网格 grid 中&#xff0c;我们放置了一些与 x&#xff0c;y&#xff0c;z 三轴对齐的 1 x 1 x 1 立方体。 每个值 v grid[i][j] 表示 v 个正方体叠放在单元格 (i, j) 上。 现在&#xff0c;我们查看这些立方体在 xy 、yz 和 zx 平面上的投…

【Java八股文总结】之Java设计模式

文章目录Java设计模式一、设计模式概述1、什么是设计模式&#xff1f;2、设计模式的6大原则3、具体的设计模式1、单例模式Q&#xff1a;为什么使用两个 if (singleton null) 进行判断&#xff1f;Q&#xff1a;volatile 关键字的作用&#xff1f;2、原型模式补充&#xff1a;浅…

yml中无法解析类 ‘HikariDataSource‘

目录 yml中无法解析类 HikariDataSource ⭐关于HikariDataSource的信息 yml中无法解析类 HikariDataSource 修改之前该行是报红的 具体代码 # 配置项目信息 spring:profiles:active: prod # yml中配置文件的环境配置&#xff0c;dev&#xff1a;开发环境&#xff0c;t…

06_通信过程

知识点1【通信过程概述】 2、PC和集线器Hub 2、PC机和交换机switch 2、路由器&#xff08;重要哟&#xff09; 知识点1【通信过程概述】 1、PacketTracer5.exe 安装 一路next 2、PC和集线器Hub 选择集线器 选择主机&#xff1a; 选择线 一个集线器4台主机&#xff1a; 配…

ZooKeeper教程

官网&#xff1a;Apache ZooKeeper 什么是Zookeeper&#xff1f; ZooKeeper是一个集中服务&#xff0c;用于维护配置信息、命名、提供分布式同步和组服务。所有这些类型的服务都以某种形式被分布式应用程序使用。每次实施它们时&#xff0c;都要进行大量的工作来修复不可避免的…