文章目录

  • 1. 简介
    • 1.1 NoSQL和MongoDB
    • 1.2 MongoDB特点
      • 1.2.1 MongoDB 技术优势
      • 1.2.2 Json 模型快速特性
    • 1.3 MongoDB 应用场景
    • 1.4 MongoDB与MongoDB Tool安装
      • Mongo安装
    • 1.5 MongoDB数据导入导出
      • 导入
      • 导出
  • 2. MongoDB 基本操作
    • 2.1 MongoDB创建数据
    • 2.2 Collections简介与基础操作
  • 3. Query
    • 3.1 Limit 与 Count
    • 3.2 Sort 与 Skip
    • 3.3 `$gt` 与 `$lt`
    • 3.4 `$or`(或)、`$and`(并)、`$all`(全)、`$in`(有)
      • 3.4.1 `$or`(或)、`$and`(并)
      • 3.4.2 `$all`(全)、`$in`(有)
  • 4. 修改数据
    • 4.1 字符操作
      • 4.1.1 `$set` 修改
      • 4.1.2 `$unset` 删除字段
      • 4.1.3 `$inc` 增加
    • 4.2 Arrays : `$push $pull` 增加 / 移除
      • 4.2.1 `$push`
    • 4.3 delete
  • 5、MongoDB整合SpringBoot集合操作
    • 5.1 环境准备
    • 5.2 集合操作
  • 6、MongoDB整合SpringBoot文档操作
    • 6.1 文档操作-相关注解
    • 6.2 查询文档
    • 6.3 更新文档
    • 6.4 删除操作

1. 简介

1.1 NoSQL和MongoDB

NoSQL与SQL的区别:

  • SQL(关系型数据库)

    • 关系型网格充分的体现了现实事务。
      一般由rows、columns组成tables,而各个tables之间又存在一定的关系Relations
      一个table包含着另一个和他有关系的数据比如说teams和players表中就存在这种关系。
    • 语句遵循SQL(Structured Query Language)例如
      SELECT FROM players WHERE last name “yzzy”;
  • NoSQL
    • 没有关系
    • 不支持SQL
    • 查询数据手段多样,比如Http,JavaScript来查询

1.2 MongoDB特点

  • 灵活易于扩展
  • 分布式文档数据库
  • 支持json与bson格式
  • 使用JavaScripti语句
  • 事务的ACID

1.2.1 MongoDB 技术优势

MongoDB基于灵活的JSON文档模型(从错综复杂的关系模型到对象模型)。适合敏捷开发,能快速响应业务变化。其高可用(自恢复、多中心容灾能力)、高水平扩展能力(横向无缝扩展,多种数据分布策略,分片,TB-PB级别)使得在处理海量、高并发的数据应用时颇具优势。

1.2.2 Json 模型快速特性

  • 数据库引擎只需要写在一个存储区
  • 反范式、无关联的组织极大优化查询速度
  • 程序API自然,开发快速

1.3 MongoDB 应用场景

游戏(积分、装备);物流(订单);社交(朋友圈、地点、附近的人);物联网(设备信息、日志);视频直播(用户信息、礼物);大数据;CRM、ERP

1.4 MongoDB与MongoDB Tool安装

Mongo安装

  • 下载网址https:/www.mongodb.com/try/download/community
    windows下载msi,其他操作系统可以参考文档
  • 安装时注意
    Data Directory:数据存放的路径;Log Dirctory:日志存放的路径;MongoDB Compass(界面化数据库管理工具)建议安装
    检测安装是否成功WIN+R输入“services.msc"指令查看是否服务启动

1.5 MongoDB数据导入导出

导入

命令:mongoimport --db dbname --type json xxx.json(也可以是jsonArray的形式导入)
示例:mongoimport --db test_db --type json product.json
以下是 products.json 文件

{ "_id" : "ac3", "name" : "AC3 Phone", "brand" : "ACME", "type" : "phone", "price" : 200, "rating" : 3.8,"warranty_years" : 1, "available" : true }
{ "_id" : "ac7", "name" : "AC7 Phone", "brand" : "ACME", "type" : "phone", "price" : 320, "rating" : 4,"warranty_years" : 1, "available" : false }
{ "_id" : { "$oid" : "507d95d5719dbef170f15bf9" }, "name" : "AC3 Series Charger", "type" : [ "accessory", "charger" ], "price" : 19, "rating" : 2.8,"warranty_years" : 0.25, "for" : [ "ac3", "ac7", "ac9" ] }
{ "_id" : { "$oid" : "507d95d5719dbef170f15bfa" }, "name" : "AC3 Case Green", "type" : [ "accessory", "case" ], "color" : "green", "price" : 12, "rating" : 1,"warranty_years" : 0 }
{ "_id" : { "$oid" : "507d95d5719dbef170f15bfb" }, "name" : "Phone Extended Warranty", "type" : "warranty", "price" : 38, "rating" : 5,"warranty_years" : 2, "for" : [ "ac3", "ac7", "ac9", "qp7", "qp8", "qp9" ] }

导出

命令:mongoexport -d dbname -c collectionname -o filepath --type json/csv -f field
示例:mongoexport -d test_db -products -o pro.json

2. MongoDB 基本操作

2.1 MongoDB创建数据

查看有那些数据库show dbs
获取当前数据库的名称
创建数据库use db name
创建一个Collections Document
db.cool_new_stff.insertOne()db.cool.new.stff.insertOne()

show dbsadmin    180.00 KiBconfig    72.00 KiBlocal     72.00 KiBtest_db  232.00 KiB
show databasesadmin    180.00 KiBconfig    72.00 KiBlocal     72.00 KiBtest_db  232.00 KiB
use example'switched to db example'
doc = {"title":"A","desc":"demo"}{ title: 'A', desc: 'demo' }
db.name.insertOne(doc);{ acknowledged: true,insertedId: ObjectId("6357ed0001c87366b1808a96") }
db.name.insertOne(doc);{ acknowledged: true,insertedId: ObjectId("6357ed0001c87366b1808a96") }
db.cool_new_stff.insertOne({});{ acknowledged: true,insertedId: ObjectId("6357ee6601c87366b1808a97") }
db.cool.new.stff.insertOne({});{ acknowledged: true,insertedId: ObjectId("6357ee7f01c87366b1808a98") }
show collectionscool_new_stffcool.new.stffname

2.2 Collections简介与基础操作

  1. 清屏cls
  2. 切换到数据库查找

查找所有:db.products.find()
查找类型等于’service’的:db.products.find({“type”:“service”});
格式化显示:db.products.find({“type”:“service”}).pretty();
只展示type该字段:db.products.find({),{“type”:1});
db.products.find({“type”:“service”),{“type”:1}).pretty();
支持正则表达式
db.products.find({“name”:{$regex:/phone/i}})
db.products.find({“name”:{Sregex:/phone/i)),{“type”:1))

db.name.find()
{ _id: ObjectId("6357ed0001c87366b1808a96"), title: 'A', desc: 'demo' }
{ _id: ObjectId("6357f15101c87366b1808a9c"), type: 123 }
{ _id: ObjectId("6357f17e01c87366b1808a9e"), name: 'Huathy' }
db.name.find({"type":"1"})
db.name.find({"type":1})
db.name.find({"type":123})
{ _id: ObjectId("6357f15101c87366b1808a9c"), type: 123 }

3. Query

3.1 Limit 与 Count

db.name.find({"title":"A"}).count()4
db.name.find({"title":"A"}).limit(2){ _id: ObjectId("6357ed0001c87366b1808a96"),title: 'A',desc: 'demo' }{ _id: ObjectId("6357f13001c87366b1808a99"),title: 'A',desc: 'demo' }

3.2 Sort 与 Skip

注意:skip()对于count()无效。也就是说不论是否跳过,统计总数都不变。

db.names.find(){ _id: ObjectId("63593ffb2108eeaab632e0d8"), num: 11 }{ _id: ObjectId("635940002108eeaab632e0d9"), num: 22 }{ _id: ObjectId("635940032108eeaab632e0da"), num: 33 }
db.names.find().sort({"num":1}){ _id: ObjectId("63593ffb2108eeaab632e0d8"), num: 11 }{ _id: ObjectId("635940002108eeaab632e0d9"), num: 22 }{ _id: ObjectId("635940032108eeaab632e0da"), num: 33 }
db.names.find().sort({"num":-1}){ _id: ObjectId("635940032108eeaab632e0da"), num: 33 }{ _id: ObjectId("635940002108eeaab632e0d9"), num: 22 }{ _id: ObjectId("63593ffb2108eeaab632e0d8"), num: 11 }
db.names.find().sort({"num":-1}).skip(2){ _id: ObjectId("63593ffb2108eeaab632e0d8"), num: 11 }

3.3 $gt$lt

注意:大于小于比较的前提是有该字段,如果没有这个字段的记录,则不会被查询出来。(这里建议设置collections的标准)

db.names.find(){ _id: ObjectId("63593ffb2108eeaab632e0d8"), num: 11 }{ _id: ObjectId("635940002108eeaab632e0d9"), num: 22 }{ _id: ObjectId("635940032108eeaab632e0da"), num: 33 }{ _id: ObjectId("635940082108eeaab632e0db"), num: 44 }{ _id: ObjectId("6359400c2108eeaab632e0dc"), num: 55 }{ _id: ObjectId("635942d72108eeaab632e0dd"), name: 'Huathy' }
db.names.find({"num":{$gt:33}}){ _id: ObjectId("635940082108eeaab632e0db"), num: 44 }{ _id: ObjectId("6359400c2108eeaab632e0dc"), num: 55 }
db.names.find({"num":{$lt:33}}){ _id: ObjectId("63593ffb2108eeaab632e0d8"), num: 11 }{ _id: ObjectId("635940002108eeaab632e0d9"), num: 22 }

3.4 $or(或)、$and(并)、$all(全)、$in(有)

3.4.1 $or(或)、$and(并)

db.prod.find({$or:[{"price":{$lt:100}},{"rating":{$gt:3}}]});{ _id: ObjectId("63594a882108eeaab632e0de"),price: 55,rating: 1 }{ _id: ObjectId("63594a8c2108eeaab632e0df"),price: 55,rating: 2 }{ _id: ObjectId("63594a902108eeaab632e0e0"),price: 55,rating: 3 }{ _id: ObjectId("63594aa02108eeaab632e0e2"),price: 205,rating: 4 }
db.prod.find({$and:[{"price":{$lt:100}},{"rating":{$gt:2}}]});{ _id: ObjectId("63594a902108eeaab632e0e0"),price: 55,rating: 3 }
db.prod.find({$and:[{"price":{$lt:100}},{"rating":{$gt:2}}]}).count();1

3.4.2 $all(全)、$in(有)

-- 所有都要满足
db.prod.find({"price":{$all:[55,100]}});{ _id: ObjectId("635c000507b9962cd04d608f"),price: [ 55, 100 ] }
-- 满足条件之一即可
db.prod.find({"price":{$in:[55,105]}});{ _id: ObjectId("63594a882108eeaab632e0de"),price: 55,rating: 1 }{ _id: ObjectId("63594a962108eeaab632e0e1"),price: 105,rating: 3 }{ _id: ObjectId("635c000507b9962cd04d608f"),price: [ 55, 100 ] }

4. 修改数据

update修改所有;updateOne修改一个

4.1 字符操作

4.1.1 $set 修改

db.prod.find(){ _id: ObjectId("63594aa02108eeaab632e0e2"),price: 205,rating: 4 }-- 条件属性和值 设置 修改的属性和值
db.prod.updateOne({"rating":4},{$set:{"price":100}}){ acknowledged: true,insertedId: null,matchedCount: 1,modifiedCount: 1,upsertedCount: 0 }
db.prod.find(){ _id: ObjectId("63594aa02108eeaab632e0e2"),price: 100,rating: 4 }

4.1.2 $unset 删除字段

db.prod.updateOne({"rating":4},{$unset:{"price":1}}){ acknowledged: true,insertedId: null,matchedCount: 1,modifiedCount: 1,upsertedCount: 0 }
db.prod.find(){ _id: ObjectId("63594aa02108eeaab632e0e2"), rating: 4 }

4.1.3 $inc 增加

db.prod.updateOne({"rating":4},{$inc:{"price":1000}}){ acknowledged: true,insertedId: null,matchedCount: 1,modifiedCount: 1,upsertedCount: 0 }
db.prod.find(){ _id: ObjectId("63594aa02108eeaab632e0e2"),rating: 4,price: 1000 }
-- 如果再次执行操作,则会再次增加
db.prod.updateOne({"rating":4},{$inc:{"price":1000}})
db.prod.find()
{ _id: ObjectId("63594aa02108eeaab632e0e2"),rating: 4,price: 2000 }

4.2 Arrays : $push $pull 增加 / 移除

4.2.1 $push

db.prod.find(){ _id: ObjectId("635c000507b9962cd04d608f"),price: [ 55, 100 ],rating: 5 }
db.prod.updateOne({"rating":5},{$push:{"price":1000}}){ acknowledged: true,insertedId: null,matchedCount: 1,modifiedCount: 1,upsertedCount: 0 }
db.prod.find(){ _id: ObjectId("635c000507b9962cd04d608f"),price: [ 55, 100, 1000 ],rating: 5 }

4.3 delete

  1. 尽量少删除元素,而是采用逻辑删除,用deleted字段来标识。
  2. delete删除所有,deleteOne删除一个
  3. 建议按照_id来删除元素。
db.prod.deleteOne({"_id":"63594a8c2108eeaab632e0df"}){ acknowledged: true, deletedCount: 0 }
db.prod.find({"_id":"63594a8c2108eeaab632e0df"})

5、MongoDB整合SpringBoot集合操作

5.1 环境准备

  1. 引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
  1. 配置yml
spring:data:mongodb:uri: mongodb://admin:123456@172.25.196.210:27017/example?authSource=admin
  1. 使用的时候注入mongoTemplate
@Autowired
private MongoTemplate mongoTemplate;

5.2 集合操作

public class MongoTest1 extends ApplicationTests{@Autowiredprivate MongoTemplate mongoTemplate;/*** 创建集合*/@Testpublic void test_create(){boolean exists = mongoTemplate.collectionExists("emp");if(exists){mongoTemplate.dropCollection("emp");}mongoTemplate.createCollection("emp");}
}

6、MongoDB整合SpringBoot文档操作

6.1 文档操作-相关注解

  1. @Document

    • 修饰范围:Class类
    • 作用:用来映射这个类的一个对象为mongo中的一条文档数据
    • 属性:(value、collection)用来指定操作的集合
  2. @Id
    • 修饰范围:成员变量、方法
    • 作用:用来将成员变量的值映射为文档的_id的值
  3. @Id
    • 修饰范围:成员变量、方法
    • 作用:将成员变量及其值,映射为文档中的一个Key:Value对。
    • 属性:(name、value)用来指定在文档中key的名称,默认为成员变量名
  4. @Transient
    • 修饰范围:成员变量、方法
    • 作用:指定此成员变量不参与文档序列号

测试:

@Test
public void test_insert() {Employee employee = new Employee(1001, "Huathy", 20, 10000.0, new Date());Employee emp1 = mongoTemplate.insert(employee);System.out.println("TEST 1 ==> " + emp1.toString());Employee emp2 = mongoTemplate.save(employee);System.out.println("TEST 2 ==> " + emp2.toString());List<Employee> list = Arrays.asList(new Employee(1002, "嘻嘻", 20, 20000.0, new Date()),new Employee(1003, "嘿嘿", 20, 30000.0, new Date()));Collection<Employee> coll = mongoTemplate.insert(list, Employee.class);coll.forEach(c -> {System.out.println("COLL INSERT ==> " + c.toString());});
}

注意:通过SpringDataMongoDB还会给集合中增加一个class属性,存储新增时Document对应Java中类的全路径。这么做是为了查询的时候能够把Doucument转为Java类型。

6.2 查询文档

Criteria是标准查询的接口。可以引用静态的Criteria.where把多个条件组合到一起,就可以轻松的将多个方法标准和查询条件相结合,方便查询语句操作。

Criteria MongoDB 说明
and $and 并且
andOperator $and 并且
orOperator $or 或者
gt $gt 大于
gte $gte 大于等于
in $in 包含
is $is 等于
lt $lt 小于
lte $lte 小于等于
nin $nin 不包含

测试:

/*** 查询与条件查询*/
@Test
public void test_find() {System.out.println("==============查询所有文档=================");List<Employee> allemps = mongoTemplate.findAll(Employee.class);List<Employee> allemps2 = mongoTemplate.find(new Query(), Employee.class);allemps.forEach(e -> {System.out.println(e.toString());});System.out.println("============根据_id查询===================");Employee emp1 = mongoTemplate.findById(1001, Employee.class);System.out.println("findByID ==> " + emp1.toString());System.out.println("============findOne返回第一个文档===================");Employee emp2 = mongoTemplate.findOne(new Query(), Employee.class);System.out.println("findOne ==> " + emp2.toString());System.out.println("============find 单 条件查询===================");Query query = new Query(Criteria.where("salary").gt(6000).lte(10000));List<Employee> emps3 = mongoTemplate.find(query, Employee.class);emps3.forEach(e -> {System.out.println("find ==> " + e.toString());});System.out.println("============find 多 条件查询===================");Criteria criteria = new Criteria();criteria.orOperator(Criteria.where("salary").gt(6000).lte(10000), Criteria.where("name").regex("嘻"));List<Employee> emps4 = mongoTemplate.find(new Query(criteria), Employee.class);emps4.forEach(e -> {System.out.println("find ==> " + e.toString());});
}/*** 排序与分页*/
@Test
public void test_sort() {System.out.println("============ sort ===================");Query query = new Query();query.with(Sort.by(Sort.Order.desc("salary")));List<Employee> emps = mongoTemplate.find(query, Employee.class);emps.forEach(e -> {System.out.println("order find ==> " + e.toString());});System.out.println("============ page ===================");// skip limit 分页。skip跳过记录数、limit限定返回结果数Query query1 = new Query();query1.with(Sort.by(Sort.Order.desc("salary"))).skip(0).limit(1);List<Employee> emps1 = mongoTemplate.find(query1, Employee.class);emps1.forEach(e -> {System.out.println("limit find ==> " + e.toString());});
}@Test
public void test_findByJson() {System.out.println("========等值查询=========");String eqJson = "{name:'Huathy'}";Query query1 = new BasicQuery(eqJson);List<Employee> emps1 = mongoTemplate.find(query1, Employee.class);emps1.forEach(System.out::println);System.out.println("========多条件查询=========");String json = "{ $or:[{age:{$gte:22}},{salary:{$gt:10000}}] }";Query query = new BasicQuery(json);List<Employee> emps = mongoTemplate.find(query, Employee.class);emps.forEach(System.out::println);
}

6.3 更新文档

在MongoDB中无论使用客户端API还是使用SpringData,更新返回结果一定是受影响行数。若更新后的结果与更新前的结果相同,则返回0;

  • updateFirst():只更新满足条件的第一条记录
  • updateMulti():更新所有满足条件的记录
  • upsert():没有符合条件的记录则插入一条数据

测试:

@Test
public void test_update(){Query query = new Query(Criteria.where("salary").is(10000));List<Employee> emps = mongoTemplate.find(query, Employee.class);System.out.println("=========== 更新前 ==========");emps.forEach(System.out::println);Update update = new Update();update.set("salary",12000);UpdateResult res1 = mongoTemplate.updateFirst(query, update, Employee.class);System.out.println("更新成功记录数1 => " + res1);UpdateResult res2 = mongoTemplate.updateMulti(query, update, Employee.class);System.out.println("更新成功记录数2 => " + res2);Query query1 = new Query(Criteria.where("salary").is(50000));List<Employee> emps1 = mongoTemplate.find(query1, Employee.class);emps1.forEach(System.out::println);System.out.println("=========== 更新前 ==========");UpdateResult res3 = mongoTemplate.upsert(query, update, Employee.class);System.out.println("更新成功记录数3 => " + res3);
}

6.4 删除操作

测试:

@Test
public void test_delete(){//        删除所有文档   // 不如使用dorpCollection()
//        mongoTemplate.remove(new Query(),Employee.class);Query query = new Query(Criteria.where("salary").is(12000));DeleteResult res = mongoTemplate.remove(query, Employee.class);System.out.println("删除记录数 => " + res);
}

MongoDB入门学习(一)简介与基本操作、整合SpringBoot集合操作、整合SpringBoot文档操作相关推荐

  1. MongoDB入门学习(三):MongoDB的增删查改

    对于我们这样的菜鸟来说,最重要的不是数据库的管理,也不是数据库的性能,更不是数据库的扩展,而是怎么用好这款数据库,也就是一个数据库提供的最核心的功能,增删查改. 由于MongoDB存储数据都是以文档的 ...

  2. 商城项目(六)整合Mongodb实现文档操作

    商城项目(六)整合Mongodb实现文档操作 整合Mongodb的过程,以实现商品浏览记录在Mongodb中的添加.删除.查询为例. 环境搭建 Mongodb Mongodb是为快速开发互联网Web应 ...

  3. mongodb 输出数组字段_MongoDb文档操作、索引操作

    学习主题:MongoDb 学习目标: 掌握mongodb文档的更新 掌握mongodb文档的删除 掌握mongodb文档的查找 掌握mongodb文档的条件操作符 掌握mongodb中的索引操作 Mo ...

  4. iOS入门、还是老手,都是值得收藏的一个资源文档

    这里写自定义目录标题 SwiftUI 资源 学习架构/模式相关开源框架 代码质量检查框架/工具 马上过年了,可以利用年假期间学习一下:对于老手来说,本文排版也可以优化,为了方便,对于github开源项 ...

  5. mongodb文档操作

    增 #1.没有指定_id则默认ObjectId,_id不能重复,且在插入后不可变#2.插入单条 user0={"name":"egon","age&q ...

  6. 整合swagger2生成Restful Api接口文档

    整合swagger2生成Restful Api接口文档 swagger Restful文档生成工具 2017-9-30 官方地址:https://swagger.io/docs/specificati ...

  7. MongoDB 数据库、集合创建删除与文档插入

    本文章主要介绍mongodb的基本命令,前提条件,你的本地已经安装了mongo. 一.基本命令使用(主要是创建,增删改.) 0.mongoDb统计信息 获得关于MongoDB的服务器统计,需要在Mon ...

  8. linux 文档操作,Linux学习之文档操作

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? The Linux Command Line 学习翻译 mkdir The mkdir command is used ...

  9. 基于Java毕业设计学习自律养成小程序后台源码+系统+mysql+lw文档+部署软件

    基于Java毕业设计学习自律养成小程序后台源码+系统+mysql+lw文档+部署软件 基于Java毕业设计学习自律养成小程序后台源码+系统+mysql+lw文档+部署软件 本源码技术栈: 项目架构:B ...

最新文章

  1. T-SQL Convert转换时间类型
  2. 【2016-05-19】一次tomcat频繁挂掉的问题定位
  3. iOS使用AVCaptureSession自定义相机
  4. php把400个数组建二维,请教怎么将多维数组转换为二维数组
  5. 不经保存,直接读取上传文件
  6. 2018年数学建模美赛B题做题思路和详细分析(五)
  7. the browsermob-proxy server process failed to start
  8. dsoframer java_[转]内嵌WORD/OFFICE的WINFORM程序——DSOFRAMER使用小结
  9. python内置函数系列之str(一)(持续更新)
  10. 推荐给大家一个网络工程标书模版(仅供参考)
  11. 怎么把kux格式转换成mp4?完美转换优酷kux格式
  12. 30个银行的logo
  13. 纯CSS实现的3D翻页效果
  14. 慢牛股票-基于Sencha touch+Cordova的股票类APP
  15. 重庆大学明月科创班课程记录1.1大一上自然与设计-仿生蝗虫设计(Solidworks)
  16. 【跟着江科大学Stm32】STM32F103C6T6_实现呼吸灯_代码
  17. 最全Java项目合集(附源码课件),可完美运行
  18. 警惕股票连续涨停后的跌停情况,需再三注意!(实例)
  19. 十八 、 View 的工作原理(2)---理解 MeasureSpec
  20. 从微信小程序看前端代码安全

热门文章

  1. Mac中LaTeX的一些用法备忘录
  2. AllPay(欧付宝)支付接口集成
  3. mysql 语句中 sum函数求和 null 变 0
  4. 统计一周内每天销售总额SQL语句
  5. Java版中文分词 IKAnalyzer
  6. 漫谈测试人生:软件测试的墨菲定律、二八定律和木桶定律…
  7. 阿胶制作方法html,一种阿胶糕灌装机的制作方法
  8. Python代码加密方案
  9. 解释以下linux命令的含义,无法理解linux命令的输出
  10. 【webapp的优化整理】要做移动前端优化的朋友进来看看吧