黑*头条_第2章_文章列表前端成形与后端变身
创始人
2024-02-01 12:53:59

黑*头条_第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.loaddirlet 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=idsresolve(d);}).catch((e)=>{reject(e);})})}},// 区别请求那个URLgetLoadUrl : 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,// APIshownew: 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.loaddirlet 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=idsresolve(d);}).catch((e)=>{reject(e);})})}},// 区别请求那个URLgetLoadUrl : 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;elsereturn config.urls.getBase()+"/"+name;}},style:{main_bg:'#3296fa'}}
export default config

2.4 实现VIEW

Index.vue中template部分

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


样式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){}
}

实现代码


完整的代码:


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连接信息
  • 一些性能优化等管理信息

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

123456itheima-news

schema.xml
  • 配置Mycat节点信息
  • 配置Mycat主机信息
  • 配置分表策略
  • 一些连接信息或者读写分离主机配置

定义数据节点:


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

定义主机节点

select user()
  • name:主机节点名称
  • minCon:最小连接线程
  • maxCon:最大连接线程
  • balance:负载均衡策略
  • writeHost 该标签配置节点对应的mysql主机

定义表信息

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

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

  • 配置 function class指定分片算法 本例为求余算法
  • 配置 tableRule 设置对那个字段执行分片操作 本例为id对40求余
	40idmod-long40
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元素中增加以下代码:
service-mycat
  • 在service-mycat pom.xml中增加父项目信息:
heima-leadnewscom.heima1.0-SNAPSHOT

  • 在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中做以下依赖配置

com.mycatmycat-server1.0.0system${basedir}/libs/Mycat-server-1.6-release.jar

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中,定义代码为:

burstburst5050
5000000005050

4.5.2 Burst4040

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

burstburst4040
4000000004040

4.5.3 规则使用

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

在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 into ap_likes_behavior (id, behavior_entry_id, entry_id, type, operation, created_time,burst)values (#{id}, #{behaviorEntryId}, #{entryId}, #{type}, #{operation},#{createdTime},#{burst})

4.7.4 程序-数据查询

  • 【问题】

数据查询的问题有两点:

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

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

  • 【解决方案】

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

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

  • 【示例】

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


以上代码在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之后,需要使用注解,以便实现注解的托管生成。

/*!mycat:catlet=io.mycat.route.sequence.BatchInsertSequence */insert into ap_show_behavior ( entry_id, article_id,is_click, show_time, created_time) values      (#{entryId}, #{item},0, now(),now())  

ation},#{createdTime},#{burst})

4.7.4 程序-数据查询

  • 【问题】

数据查询的问题有两点:

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

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

  • 【解决方案】

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

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

  • 【示例】

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


以上代码在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之后,需要使用注解,以便实现注解的托管生成。

/*!mycat:catlet=io.mycat.route.sequence.BatchInsertSequence */insert into ap_show_behavior ( entry_id, article_id,is_click, show_time, created_time) values      (#{entryId}, #{item},0, now(),now())  

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...