IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 创建项目并初始化业务数据——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(六) -> 正文阅读

[大数据]创建项目并初始化业务数据——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(六)

系列文章目录

  1. 初识推荐系统——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(一)
  2. 利用用户行为数据——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(二)
  3. 项目主要效果展示——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(三)
  4. 项目体系架构设计——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(四)
  5. 基础环境搭建——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(五)
  6. 创建项目并初始化业务数据——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(六)
  7. ……

项目资源下载

  1. 电影推荐系统网站项目源码Github地址(可Fork可Clone)
  2. 电影推荐系统网站项目源码Gitee地址(可Fork可Clone)
  3. 电影推荐系统网站项目源码压缩包下载(直接使用)
  4. 电影推荐系统网站项目源码所需全部工具合集打包下载(spark、kafka、flume、tomcat、azkaban、elasticsearch、zookeeper)
  5. 电影推荐系统网站项目源数据(可直接使用)
  6. 电影推荐系统网站项目个人原创论文
  7. 电影推荐系统网站项目前端代码
  8. 电影推荐系统网站项目前端css代码


前言

??今天给大家带来的博文是关于代码项目的初始化以及整个项目所需数据的初始化,其中包括,在 I D E A IDEA IDEA中创建 m a v e n maven maven项目、数据加载准备、数据初始化到 M o n g o D B MongoDB MongoDB、数据初始化到 E l a s t i c S e a r c h ElasticSearch ElasticSearch等内容,通过这篇博文我们就可以把整个项目的框架搭建起来了。另外有一点很重要,关于代码的内容大家要注意可能和我的命名不同,当然允许不同,但是要注意修改相关的位置,相信能做到这里的读者应该都是有一定基础的,但还是要提醒一下,需要注意。当然,读者还是要有Scala和Maven的基础。下面就开始今天的学习吧!


一、在 I D E A IDEA IDEA中创建 M a v e n Maven Maven项目

??项目主体用 S c a l a Scala Scala编写,采用 I D E A IDEA IDEA作为开发环境进行项目编写,采用 M a v e n Maven Maven作为项目构建和管理工具
??首先打开 I D E A IDEA IDEA,创建一个 M a v e n Maven Maven项目,命名为MovieRecommendSystem。为了方便后期的联调,会把业务系统的代码也添加进来,所以可以以MovieRecommendSystem作为父项目,并在其下建一个名为recommender的子项目,然后再在下面搭建多个子项目用于提供不同的推荐服务。

1.1 项目框架搭建

??在MovieRecommendSystem的pom.xml文件中加入元素<packaging>pom</packaging>,然后新建一个maven module。子项目的第一步是初始化业务数据,所以子项目命名为DataLoader
??父项目只是为了规范化项目结构,方便依赖管理,本身是不需要代码实现的,所以MovieRecommendSystem和recommender下的src文件夹都可以删掉
??目前的整体项目框架如下:
在这里插入图片描述

1.2 声明项目中工具的版本信息

??整个项目需要用到多个工具,它们的不同版本可能会对程序运行造成影响,所以应该在最外层的MovieRecommendSystem中声明所有子项目共用的版本信息。在MovieRecommendSystem/pom.xml中加入以下配置:

<properties>
	<log4j.version>1.2.17</log4j.version>
	<slf4j.version>1.7.22</slf4j.version>
	<mongodb-spark.version>2.0.0</mongodb-spark.version>
	<casbah.version>3.1.1</casbah.version>
	<elasticsearch-spark.version>5.6.2</elasticsearch-spark.version>
	<elasticsearch.version>5.6.2</elasticsearch.version>
	<redis.version>2.9.0</redis.version>
	<kafka.version>0.10.2.1</kafka.version>
	<spark.version>2.1.1</spark.version>
	<scala.version>2.11.8</scala.version>
	<jblas.version>1.2.1</jblas.version>
</properties>

1.3 添加项目依赖

??首先,对于整个项目而言,应该有同样的日志管理,在MovieRecommendSystem中引入公有依赖:

<dependencies>
	<!—引入共同的日志管理工具 -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>jcl-over-slf4j</artifactId>
		<version>${slf4j.version}</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
		<version>${slf4j.version}</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
		<version>${slf4j.version}</version>
	</dependency>
	<dependency>
		<groupId>log4j</groupId>
		<artifactId>log4j</artifactId>
		<version>${log4j.version}</version>
	</dependency>
</dependencies>

??同样,对于maven项目的构建,可以引入公有的插件:

<build>
	<!--声明并引入子项目共有的插件-->
	<plugins>
		<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.6.1</version>
				<!--所有的编译用 JDK1.8-->
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
		</plugin>
	</plugins>
	<pluginManagement>
		<plugins>
			<!--maven 的打包插件-->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-assembly-plugin</artifactId>
				<version>3.0.0</version>
				<executions>
					<execution>
						<id>make-assembly</id>
						<phase>package</phase>
						<goals>
							<goal>single</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<!--该插件用于将 scala 代码编译成 class 文件-->
			<plugin>
				<groupId>net.alchim31.maven</groupId>
				<artifactId>scala-maven-plugin</artifactId>
				<version>3.2.2</version>
				<executions>
				<!--绑定到 maven 的编译阶段-->
				<execution>
					<goals>
						<goal>compile</goal>
						<goal>testCompile</goal>
					</goals>
				</execution>
				</executions>
			</plugin>
		</plugins>
	</pluginManageme>
</build>

??然后,在MovieRecommendSystem/recommender/ pom.xml模块中,可以为所有的推荐模块声明spark相关依赖(这里的dependencyManagement表示仅声明相关信息,子项目如果依赖需要自行导入)

<dependencyManagement>
    <dependencies>
        <!-- 引入Spark相关的Jar包 -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-mllib_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-graphx_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
		<dependency>
		    <groupId>org.scala-lang</groupId>
			<artifactId>scala-library</artifactId>
			<version>${scala.version}</version>
		</dependency>
    </dependencies>
</dependencyManagement>

??由于各推荐模块都是scala代码,还应该引入scala-maven-plugin插件,用于scala程序的编译。因为插件已经在父项目中声明,所以这里不需要再声明版本和具体配置:

<build>
    <plugins>
        <!-- 父项目已声明该plugin,子项目在引入的时候,不用声明版本和已经声明的配置 -->
        <plugin>
            <groupId>net.alchim31.maven</groupId>
            <artifactId>scala-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

??对于具体的DataLoader子项目,需要spark相关组件,还需要mongodb的相关依赖,在MovieRecommendSystem/recommender/DataLoader/pom.xml文件中引入所有依赖(在父项目中已声明的不需要再加详细信息):

<dependencies>
    <!-- Spark的依赖引入 -->
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.11</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_2.11</artifactId>
    </dependency>
    <!-- 引入Scala -->
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
    </dependency>
    <!-- 加入MongoDB的驱动 -->
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>casbah-core_2.11</artifactId>
        <version>${casbah.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mongodb.spark</groupId>
        <artifactId>mongo-spark-connector_2.11</artifactId>
        <version>${mongodb-spark.version}</version>
    </dependency>
	<!-- 加入 ElasticSearch 的驱动 -->
	<dependency>
		<groupId>org.elasticsearch.client</groupId>
		<artifactId>transport</artifactId>
		<version>${elasticsearch.version}</version>
	</dependency>
	<dependency>
		<groupId>org.elasticsearch</groupId>
		<artifactId>elasticsearch-spark-20_2.11</artifactId>
		<version>${elasticsearch-spark.version}</version>
		<!-- 将不需要依赖的包从依赖路径中除去 -->
		<exclusions>
			<exclusion>
				<groupId>org.apache.hive</groupId>
				<artifactId>hive-service</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
</dependencies>

??至此,做数据加载需要的依赖都已配置好,可以开始写代码了

二、数据加载准备

??在src/main/目录下,可以看到已有的默认源文件目录是java,可以将其改名为scala。将数据文件movies.csv、ratings.csv、tags.csv复制到资源文件目录src/main/resources下,将从这里读取数据并加载到Mongodb和Elasticsearch中

2.1 M o v i e s Movies Movies数据集

??数据格式如下:

mid,name,descri,timelong,issue,shoot,language,genres,actors,directors

??例子如下:

1^Toy Story (1995)^ ^81 minutes^March 20, 2001^1995^English
^Adventure|Animation|Children|Comedy|Fantasy ^Tom Hanks|Tim Allen|Don Rickles|Jim Varney|Wallace Shawn|John Ratzenberger|Annie Potts|John Morris|Erik von Detten|Laurie Metcalf|R. Lee Ermey|Sarah Freeman|Penn Jillette|Tom Hanks|Tim Allen|Don Rickles|Jim Varney|Wallace Shawn ^John Lasseter

??Movie数据集有10个字段,每个字段之间通过“^”符号进行分割

字段名字段类型字段描述字段备注
midInt电影的ID
nameString电影的名称
descriString电影的描述
timelongString电影的时长
shootString电影拍摄时间
issueString电影发布时间
languageArray[String]电影语言每一项用“|”分割
genresArray[String]电影所属类别每一项用“|”分割
directorArray[String]电影的导演每一项用“|”分割
actorsArray[String]电影的演员每一项用“|”分割

2.2 R a t i n g s Ratings Ratings数据集

??数据格式如下:

userId,movieId,rating,timestamp

??例子如下:

1,31,2.5,1260759144

??Rating数据集有4个字段,每个字段之间通过“,”符号进行分割

字段名字段类型字段描述字段备注
uidInt用户的ID
midInt电影的ID
scoreDouble电影的分值
timestampLong评分的时间

2.3 T a g Tag Tag数据集

??数据格式如下:

userId,movieId,tag,timestamp

??例子如下:

1,31,action,1260759144

??Tag数据集有4个字段,每个字段之间通过“,”符号进行分割

字段名字段类型字段描述字段备注
uidInt用户的ID
midInt电影的ID
tagString电影的标签
timestampLong评分的时间

2.4 日志管理配置文件

??log4j对日志的管理,需要通过配置文件来生效。在src/main/resources下新建配置文件log4j.properties,写入以下内容:

log4j.rootLogger=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}  %5p --- [%50t]  %-80c(line:%5L)  :  %m%n

三、数据初始化到 M o n g o D B MongoDB MongoDB

3.1 启动 M o n g o D B MongoDB MongoDB数据库

[bigdata@linux mongodb]$ bin/mongod -config data/mongodb.conf

3.2 数据加载程序主体实现

??为原始数据定义几个样例类,通过SparkContext的textFile方法从文件中读取数据,并转换成DataFrame,再利用Spark SQL提供的write方法进行数据的分布式插入
??在DataLoader/src/main/scala下新建package,命名为com.IronmanJay.recommender,新建名为DataLoader的scala class文件,也就是:DataLoader/src/main/scala/com.IronmanJay.recommerder/DataLoader.scala
??程序主体代码如下:

/**

  * Movie 数据集
  *
  * 260                                         电影ID,mid
  * Star Wars: Episode IV - A New Hope (1977)   电影名称,name
  * Princess Leia is captured and held hostage  详情描述,descri
  * 121 minutes                                 时长,timelong
  * September 21, 2004                          发行时间,issue
  * 1977                                        拍摄时间,shoot
  * English                                     语言,language
  * Action|Adventure|Sci-Fi                     类型,genres
  * Mark Hamill|Harrison Ford|Carrie Fisher     演员表,actors
  * George Lucas                                导演,directors
  *
  */
case class Movie(mid: Int, name: String, descri: String, timelong: String, issue: String,
                 shoot: String, language: String, genres: String, actors: String, directors: String)

/**
  * Rating数据集
  *
  * 1,31,2.5,1260759144
  */
case class Rating(uid: Int, mid: Int, score: Double, timestamp: Int)

/**
  * Tag数据集
  *
  * 15,1955,dentist,1193435061
  */
case class Tag(uid: Int, mid: Int, tag: String, timestamp: Int)

// 把mongo和es的配置封装成样例类

/**
  *
  * @param uri MongoDB连接
  * @param db  MongoDB数据库
  */
case class MongoConfig(uri: String, db: String)

/**
  *
  * @param httpHosts      http主机列表,逗号分隔
  * @param transportHosts transport主机列表
  * @param index          需要操作的索引
  * @param clustername    集群名称,默认elasticsearch
  */
case class ESConfig(httpHosts: String, transportHosts: String, index: String, clustername: String)

object DataLoader {

  // 定义常量
  val MOVIE_DATA_PATH = "D:\\Software\\MovieRecommendSystem\\recommender\\DataLoader\\src\\main\\resources\\movies.csv"
  val RATING_DATA_PATH = "D:\\Software\\MovieRecommendSystem\\recommender\\DataLoader\\src\\main\\resources\\ratings.csv"
  val TAG_DATA_PATH = "D:\\Software\\MovieRecommendSystem\\recommender\\DataLoader\\src\\main\\resources\\tags.csv"

  val MONGODB_MOVIE_COLLECTION = "Movie"
  val MONGODB_RATING_COLLECTION = "Rating"
  val MONGODB_TAG_COLLECTION = "Tag"
  val ES_MOVIE_INDEX = "Movie"

  def main(args: Array[String]): Unit = {

    val config = Map(
      "spark.cores" -> "local[*]",
      "mongo.uri" -> "mongodb://linux:27017/recommender",
      "mongo.db" -> "recommender",
      "es.httpHosts" -> "linux:9200",
      "es.transportHosts" -> "linux:9300",
      "es.index" -> "recommender",
      "es.cluster.name" -> "es-cluster"
    )

    // 创建一个sparkConf
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("DataLoader")

    // 创建一个SparkSession
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    import spark.implicits._

    // 加载数据
    val movieRDD = spark.sparkContext.textFile(MOVIE_DATA_PATH)

    val movieDF = movieRDD.map(
      item => {
        val attr = item.split("\\^")
        Movie(attr(0).toInt, attr(1).trim, attr(2).trim, attr(3).trim, attr(4).trim, attr(5).trim, attr(6).trim, attr(7).trim, attr(8).trim, attr(9).trim)
      }
    ).toDF()

    val ratingRDD = spark.sparkContext.textFile(RATING_DATA_PATH)

    val ratingDF = ratingRDD.map(item => {
      val attr = item.split(",")
      Rating(attr(0).toInt, attr(1).toInt, attr(2).toDouble, attr(3).toInt)
    }).toDF()

    val tagRDD = spark.sparkContext.textFile(TAG_DATA_PATH)
    //将tagRDD装换为DataFrame
    val tagDF = tagRDD.map(item => {
      val attr = item.split(",")
      Tag(attr(0).toInt, attr(1).toInt, attr(2).trim, attr(3).toInt)
    }).toDF()

    implicit val mongoConfig = MongoConfig(config("mongo.uri"), config("mongo.db"))

    // 将数据保存到MongoDB
    storeDataInMongoDB(movieDF, ratingDF, tagDF)

    // 数据预处理,把movie对应的tag信息添加进去,加一列 tag1|tag2|tag3...
    import org.apache.spark.sql.functions._

    /**
      * mid, tags
      *
      * tags: tag1|tag2|tag3...
      */
    val newTag = tagDF.groupBy($"mid")
      .agg(concat_ws("|", collect_set($"tag")).as("tags"))
      .select("mid", "tags")

    // newTag和movie做join,数据合并在一起,左外连接
    val movieWithTagsDF = movieDF.join(newTag, Seq("mid"), "left")

    implicit val esConfig = ESConfig(config("es.httpHosts"), config("es.transportHosts"), config("es.index"), config("es.cluster.name"))

    // 保存数据到ES
    storeDataInES(movieWithTagsDF)

    spark.stop()
  }
}

3.3 将数据写入 M o n g o D B MongoDB MongoDB

??接下来,实现storeDataInMongo方法,将数据写入Mongodb中:

def storeDataInMongoDB(movieDF: DataFrame, ratingDF: DataFrame, tagDF: 
DataFrame)(implicit mongoConfig: MongoConfig): Unit = {
  // 新建一个mongodb的连接
  val mongoClient = MongoClient(MongoClientURI(mongoConfig.uri))

  // 如果mongodb中已经有相应的数据库,先删除
  mongoClient(mongoConfig.db)(MONGODB_MOVIE_COLLECTION).dropCollection()
  mongoClient(mongoConfig.db)(MONGODB_RATING_COLLECTION).dropCollection()
  mongoClient(mongoConfig.db)(MONGODB_TAG_COLLECTION).dropCollection()

  // 将DF数据写入对应的mongodb表中
  movieDF.write
    .option("uri", mongoConfig.uri)
    .option("collection", MONGODB_MOVIE_COLLECTION)
    .mode("overwrite")
    .format("com.mongodb.spark.sql")
    .save()

  ratingDF.write
    .option("uri", mongoConfig.uri)
    .option("collection", MONGODB_RATING_COLLECTION)
    .mode("overwrite")
    .format("com.mongodb.spark.sql")
    .save()

  tagDF.write
    .option("uri", mongoConfig.uri)
    .option("collection", MONGODB_TAG_COLLECTION)
    .mode("overwrite")
    .format("com.mongodb.spark.sql")
    .save()

  //对数据表建索引
  mongoClient(mongoConfig.db)(MONGODB_MOVIE_COLLECTION).createIndex(MongoDBObject("mid" -> 1))
  mongoClient(mongoConfig.db)(MONGODB_RATING_COLLECTION).createIndex(MongoDBObject("uid" -> 1))
  mongoClient(mongoConfig.db)(MONGODB_RATING_COLLECTION).createIndex(MongoDBObject("mid" -> 1))
  mongoClient(mongoConfig.db)(MONGODB_TAG_COLLECTION).createIndex(MongoDBObject("uid" -> 1))
  mongoClient(mongoConfig.db)(MONGODB_TAG_COLLECTION).createIndex(MongoDBObject("mid" -> 1))

  mongoClient.close()

}

四、数据初始化到 E l a s t i c S e a r c h ElasticSearch ElasticSearch

4.1 启动 E l a s t i c S e a r c h ElasticSearch ElasticSearch数据库

??这里有一个小坑,如果有的读者习惯使用root用户,那么启动ElasticSearch的时候要切换为非root用户,并且这个非root用户要被授权,关闭的时候也如此,否则会出现打不开或者其他问题的现象。启动ElasticSearch的命令如下:

[bigdata@linux elasticsearch-5.6.2]$ ./bin/elasticsearch -d

4.2 将数据写入 E l a s t i c S e a r c h ElasticSearch ElasticSearch

??与上节类似,同样主要通过Spark SQL提供的write方法进行数据的分布式插入,实现storeDataInES方法:

def storeDataInES(movieDF: DataFrame)(implicit eSConfig: ESConfig): Unit
 = {
  // 新建es配置
  val settings: Settings = Settings.builder().put("cluster.name", eSConfig.clustername).build()

  // 新建一个es客户端
  val esClient = new PreBuiltTransportClient(settings)

  val REGEX_HOST_PORT = "(.+):(\\d+)".r
  eSConfig.transportHosts.split(",").foreach {
    case REGEX_HOST_PORT(host: String, port: String) => {
      esClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port.toInt))
    }
  }

  // 先清理遗留的数据
  if (esClient.admin().indices().exists(new IndicesExistsRequest(eSConfig.index))
    .actionGet()
    .isExists
  ) {
    esClient.admin().indices().delete(new DeleteIndexRequest(eSConfig.index))
  }

  esClient.admin().indices().create(new CreateIndexRequest(eSConfig.index))

  movieDF.write
    .option("es.nodes", eSConfig.httpHosts)
    .option("es.http.timeout", "100m")
    .option("es.mapping.id", "mid")
.option("es.nodes.wan.only", "true")
    .mode("overwrite")
    .format("org.elasticsearch.spark.sql")
    .save(eSConfig.index + "/" + ES_MOVIE_INDEX)
}

总结

??最近有点忙,考研复试结束之后再到拟录取之后,一直在摆(bushi),所以把这个系列给搁置了,刚刚抽出空给这次补上了,后面我争取一周一篇(立一个小小的flag)。也请大家一起努力,加油,我在下篇博客等你!哦,对了,这篇博客因为有代码,所以有些小细节大家一定要注意,比如命名之类的,一定注意!

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-04-26 11:47:11  更:2022-04-26 11:50:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:32:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码