乐优商城(09)–商品详情

一、商品详情

1.1、商品详情页服务

商品详情浏览量比较大,并发高,所以独立开发一个微服务,用来展示商品详情

创建module

商品的详情页服务,命名为:leyou-goods-web

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>myLeyou</artifactId><groupId>com.leyou.parent</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><groupId>com.leyou.goods</groupId><artifactId>leyou-goods-web</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>com.leyou.item</groupId><artifactId>leyou-item-interface</artifactId><version>0.0.1-SNAPSHOT</version></dependency></dependencies></project>

启动类

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class LeyouGoodsWebApplication {public static void main(String[] args) {SpringApplication.run(LeyouGoodsWebApplication.class,args);}
}

application.yaml

server:port: 8084
spring:application:name: goods-webthymeleaf:cache: false #关闭缓存,利于开发cloud:nacos:discovery:server-addr: ip地址:8848username: nacospassword: nacos

页面模板

从leyou-portal中复制item.html模板到当前项目resource目录下的templates中:

1.2、页面跳转

1.2.1、修改页面跳转路径

首先需要修改搜索结果页的商品地址,目前所有商品的地址都是:http://www.leyou.com/item.html

应该跳转到对应的商品的详情页才对。

那么问题来了:商品详情页是一个SKU?还是多个SKU的集合?

通过详情页的预览,知道它是多个SKU的集合,即SPU。

所以,页面跳转时,应该携带SPU的id信息。

例如:http://www.leyou.com/item/2314123.html

这里就采用了路径占位符的方式来传递spu的id,打开search.html,修改其中的商品路径:

1.2.2、nginx反向代理

接下来,要把这个地址指向刚刚创建的服务:leyou-goods-web,其端口为8084

需要在nginx.conf中添加一段配置:

把以/item开头的请求,代理到机器的8084端口。

1.2.3、编写跳转controller

leyou-goods-web中编写controller,接收请求,并跳转到商品详情页:

@RestController
@RequestMapping("/item")
public class GoodsController {/*** 跳转到商品详情页* @param id* @return*/@GetMapping("/{id}.html")public String toItemPage(@PathVariable("id")Long id){return "item";}
}

测试

启动leyou-goods-page,点击搜索页面商品,看是能够正常跳转.

现在看到的依然是静态的数据。接下来开始页面的渲染

1.3、封装模型数据

首先分析一下,在这个页面中需要哪些数据

已知的条件是传递来的spu的id,然后需要根据spu的id查询到下面的数据:

  • spu信息
  • spu的详情
  • spu下的所有sku
  • 品牌
  • 商品三级分类
  • 商品规格参数、规格参数组

1.3.1、商品微服务提供接口

以上所需数据中,根据id查询spuspu的接口目前还没有,需要在商品微服务中提供这个接口:

GoodsController

/*** 根据spu的id查询spu对象* @param id* @return*/
@GetMapping("/spu/{id}")
public ResponseEntity<Spu> querySpuById(@PathVariable("id") Long id){Spu spu = this.goodsService.querySpuById(id);if(null == spu){return ResponseEntity.notFound().build();}return ResponseEntity.ok(spu);
}

GoodsService

/*** 根据spu的id查询spu对象* @param id* @return*/
Spu querySpuById(Long id);

实现类:

/*** 根据spu的id查询spu对象** @param id* @return*/
@Override
public Spu querySpuById(Long id) {return this.spuMapper.selectByPrimaryKey(id);
}

GoodsApi

/*** 根据spu的id查询spu* @param id* @return*/
@GetMapping("spu/{id}")
Spu querySpuById(@PathVariable("id") Long id);

1.3.2、查询规格参数组

在页面展示规格时,需要按组展示:

组内有多个参数,为了方便展示。在leyou-item-service中提供一个接口,查询规格组,同时在规格组内的所有参数。

SpecificationController

/*** 查询规格参数组,及组内参数* @param cid* @return*/
@GetMapping("/groups/param/{cid}")
public ResponseEntity<List<SpecGroup>> queryGroupsParamsByCid(@PathVariable("cid") Long cid){List<SpecGroup> groups = this.specificationService.queryGroupsParamsByCid(cid);if (CollectionUtils.isEmpty(groups)){return ResponseEntity.notFound().build();}return ResponseEntity.ok(groups);
}

SpecificationService

/*** 查询规格参数组,及组内参数* @param cid* @return*/
List<SpecGroup> queryGroupsParamsByCid(Long cid);

实现类:

/*** 查询规格参数组,及组内参数** @param cid* @return*/
@Override
public List<SpecGroup> queryGroupsParamsByCid(Long cid) {//查询分组List<SpecGroup> groups = this.queryGroupsByCid(cid);groups.forEach(group ->{//查询具体规格参数group.setParams(this.queryParams(group.getId(),null,null,null));});return groups;
}

SpecificationApi

/*** 查询规格参数组,及组内参数* @param cid* @return*/
@GetMapping("/groups/param/{cid}")
List<SpecGroup> queryGroupsParamsByCid(@PathVariable("cid")Long cid);

1.4、创建FeignClient

leyou-goods-web服务中,创建FeignClient,直接将leyou-search的client的类复制过来

记得添加允许创建相同bean的配置:

spring:main:allow-bean-definition-overriding: true

1.5、封装数据模型

创建一个GoodsService,在里面来封装数据模型。

这里要查询的数据:

  • SPU

  • SpuDetail

  • SKU集合

  • 商品分类

    • 这里值需要分类的id和name就够了,因此查询到以后需要自己封装数据
  • 品牌对象

  • 规格组

    • 查询规格组的时候,把规格组下所有的参数也一并查出,上面提供的接口中已经实现该功能,直接调用
  • sku的特有规格参数

    有了规格组,为什么这里还要查询?

    因为在SpuDetail中的SpecialSpec中,是以id作为规格参数id作为key,

但是,在页面渲染时,需要知道参数的名称,如图:

需要把id和name一一对应起来,因此需要额外查询sku的特有规格参数,然后变成一个id:name的键值对格式。也就是一个Map,方便将来根据id查找!

GoodsService

public interface GoodsService {/*** 封装商品详情时所需的数据* @param spuId* @return*/Map<String, Object> loadData(Long spuId);
}

实现类:

@Service
public class GoodsServiceImpl implements GoodsService {@Autowiredprivate BrandClient brandClient;@Autowiredprivate CategoryClient categoryClient;@Autowiredprivate GoodsClient goodsClient;@Autowiredprivate SpecificationClient specificationClient;/*** 封装商品详情时所需的数据** @param spuId* @return*/@Overridepublic Map<String, Object> loadData(Long spuId) {//查询spuSpu spu = this.goodsClient.querySpuById(spuId);//查询spuDetailSpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spuId);//查询skusList<Sku> skus = this.goodsClient.querySkusBySpuId(spuId);//查询分类信息//先获取到spu的三级分类idList<Long> ids = Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3());//根据分类id查询分类名称List<String> names = this.categoryClient.queryNamesByIds(ids);//查询具体的分类信息List<Map<String,Object>> categories = new ArrayList<>();for (int i = 0; i < ids.size(); i++) {Map<String,Object> categoryMap = new HashMap<>();categoryMap.put("id",ids.get(i));categoryMap.put("name",names.get(i));categories.add(categoryMap);}//查询品牌Brand brand = this.brandClient.queryBrandByIds(Arrays.asList(spu.getBrandId())).get(0);//查询参数组List<SpecGroup> groups = this.specificationClient.queryGroupsParamsByCid(spu.getCid3());//查询特殊规格参数List<SpecParam> params = this.specificationClient.queryParams(null, spu.getCid3(), false, null);//将每个参数封装成id:val键值对形式Map<Long,String> paramMap = new HashMap<>();params.forEach(param -> paramMap.put(param.getId(),param.getName()));//初始化一个map  用于结果返回Map<String,Object> result =  new HashMap<>();// 封装spuresult.put("spu",spu);// 封装spuDetailresult.put("spuDetail",spuDetail);// 封装sku集合result.put("skus",skus);// 分类result.put("categories",categories);//品牌result.put("brand",brand);//规格参数result.put("groups",groups);//特殊规格参数result.put("paramMap",paramMap);return result;}
}

然后在controller中把数据放入model:

@Controller
@RequestMapping("/item")
public class GoodsController {@Autowiredprivate GoodsService goodsService;/*** 跳转到商品详情页* @param model* @param id* @return*/@GetMapping("{id}.html")public String toItemPage(Model model, @PathVariable("id")Long id){// 加载所需的数据Map<String, Object> modelMap = this.goodsService.loadData(id);// 放入模型model.addAllAttributes(modelMap);return "item";}
}

测试

在页面中先写一段JS,把模型中的数据取出观察,看是否成功:

<script th:inline="javascript">const a = /*[[${groups}]]*/ [];const b = /*[[${params}]]*/ [];const c = /*[[${categories}]]*/ [];const d = /*[[${spu}]]*/ {};const e = /*[[${spuDetail}]]*/ {};const f = /*[[${skus}]]*/ [];const g = /*[[${brand}]]*/ {};
</script>

然后查看页面源码:

1.6、渲染面包屑

在商品展示页的顶部,有一个商品分类、品牌、标题的面包屑

其数据有3部分:

  • 商品分类
  • 商品品牌
  • spu标题

封装的数据模型中都有,所以直接渲染即可(页面101行开始):

1.7、渲染商品列表

预期效果如下:

这个部分需要渲染的数据有5块:

  • sku图片
  • sku标题
  • 副标题
  • sku价格
  • 特有规格属性列表

其中,sku 的图片、标题、价格,都必须在用户选中一个具体sku后,才能渲染。而特有规格属性列表可以在spuDetail中查询到。而副标题则是在spu中,直接可以在页面渲染

因此,先对特有规格属性列表进行渲染。等用户选择一个sku,再通过js对其它sku属性渲染

1.7.1、副标题

1.7.2、渲染规格属性列表

格属性列表将来会有事件和动态效果。需要有js代码参与,不能使用Thymeleaf来渲染了。

因此,这里用vue,不过需要先把数据放到js对象中,方便vue使用

初始化数据

在页面的head中,定义一个js标签,然后在里面定义变量,保存与sku相关的一些数据:

<script th:inline="javascript">// sku集合const skus = /*[[${skus}]]*/ [];// 规格参数id与name对const paramMap = /*[[${params}]]*/ {};// 特有规格参数集合const specialSpec = JSON.parse(/*[[${spuDetail.specialSpec}]]*/ "");const indexes = {};Object.keys(paramMap).forEach(param => indexes[param] = 0)
</script>
  • specialSpec:这是SpuDetail中唯一与Sku相关的数据

    因此并没有保存整个spuDetail,而是只保留了这个属性,并且需要手动转为js对象。

  • paramMap:规格参数的id和name键值对,方便页面根据id获取参数名

  • skus:sku集合

  • indexes:保存被选择的规格项的索引,默认值设置为0

具体数据:

通过Vue渲染

把刚才获得的几个变量保存在Vue实例中:

然后在页面中渲染:

刷新页面:

1.7.3、确定SKU

在设计sku数据的时候,就已经添加了一个字段:indexes:

这其实就是规格参数的索引组合。

在页面中,当用户点击选择规格后,就会把对应的索引保存起来。

因此,可以根据这个indexes来确定用户要选择的sku,在vue中定义一个计算属性,来计算与索引匹配的sku:

在浏览器工具中查看:

1.7.4、渲染sku列表

既然已经拿到了用户选中的sku,接下来,就可以在页面渲染数据了

图片列表

商品图片是一个字符串,所以需要将其分割成数组,然后页面遍历即可

标题和价格

完整效果

1.8、商品详情

商品详情页面如下图所示:

分成上下两部分:

  • 上部:展示的是规格属性列表
  • 下部:展示的是商品详情

1.8.1、属性列表

1.8.2、商品详情

商品详情是HTML代码,不能使用 th:text,应该使用th:utext

1.9、规格包装

规格包装分成两部分:

  • 规格参数
  • 包装列表

而且规格参数需要按照组来显示

1.9.1、规格参数

最终效果:

数据模型中有一个groups,跟这个数据结果很像:

分成8个组,组内都有params,里面是所有的参数。不过,这些参数都没有值!

规格参数的值分为两部分:

  • 通用规格参数:保存在SpuDetail中的genericSpec中
  • 特有规格参数:保存在sku的ownSpec中

需要把这两部分值取出来,放到groups中。

从spuDetail中取出genericSpec并取出groups

把genericSpec引入到Vue实例:

因为sku是动态的,所以需要编写一个计算属性,来进行值的组合:

然后在页面渲染:

1.9.2、包装列表

包装列表在商品详情中,一开始并没有赋值到Vue实例中,但是可以通过Thymeleaf来渲染

1.9.3、售后服务

售后服务也是通过Thymeleaf进行渲染:

二、页面静态化

2.1、问题分析

现在的页面是通过Thymeleaf模板引擎渲染后返回到客户端。在后台需要大量的数据查询,而后渲染得到HTML页面。会对数据库造成压力,并且请求的响应时间过长,并发能力不高。

首先能想到的就是缓存技术,比如之前学习过的Redis。不过Redis适合数据规模比较小的情况。假如数据量比较大,例如商品详情页。每个页面如果10kb,100万商品,就是10GB空间,对内存占用比较大。此时就给缓存系统带来极大压力,如果缓存崩溃,接下来倒霉的就是数据库了。

所以缓存并不是万能的,某些场景需要其它技术来解决,比如静态化。

2.2、什么是静态化

静态化是指把动态生成的HTML页面变为静态内容保存,以后用户的请求到来,直接访问静态页面,不再经过服务的渲染。

而静态的HTML页面可以部署在nginx中,从而大大提高并发能力,减小tomcat压力。

2.3、如何实现静态化

目前,静态化页面都是通过模板引擎来生成,而后保存到nginx服务器来部署。常用的模板引擎比如:

  • Freemarker
  • Velocity
  • Thymeleaf

因为之前就使用的Thymeleaf,来渲染html返回给用户。Thymeleaf除了可以把渲染结果写入Response,也可以写到本地文件,从而实现静态化。

2.4、Thymeleaf实现静态化

2.4.1、概念

先说下Thymeleaf中的几个概念:

  • Context:运行上下文
  • TemplateResolver:模板解析器
  • TemplateEngine:模板引擎

Context

上下文: 用来保存模型数据,当模板引擎渲染时,可以从Context上下文中获取数据用于渲染。

当与SpringBoot结合使用时,我们放入Model的数据就会被处理到Context,作为模板渲染的数据使用。

TemplateResolver

模板解析器:用来读取模板相关的配置,例如:模板存放的位置信息,模板文件名称,模板文件的类型等等。

当与SpringBoot结合时,TemplateResolver已经由其创建完成,并且各种配置也都有默认值,比如模板存放位置,其默认值就是:templates。比如模板文件类型,其默认值就是html。

TemplateEngine

模板引擎:用来解析模板的引擎,需要使用到上下文、模板解析器。分别从两者中获取模板中需要的数据,模板文件。然后利用内置的语法规则解析,从而输出解析后的文件。来看下模板引擎进行处理的函数:

templateEngine.process("模板名", context, writer);

三个参数:

  • 模板名称
  • 上下文:里面包含模型数据
  • writer:输出目的地的流

在输出时,可以指定输出的目的地,如果目的地是Response的流,那就是网络响应。如果目的地是本地文件,那就实现静态化了。

而在SpringBoot中已经自动配置了模板引擎,因此不需要关心这个。现在做静态化,就是把输出的目的地改成本地文件即可!

2.5、具体实现

新建一个接口为GoodsHtmlService以及相应实现类

/*** 实现页面静态化接口*/
public interface GoodsHtmlService {/*** 实现商品详情页静态化* @param spuId*/void createHtml(Long spuId);/*** 新建线程处理页面静态化* @param spuId*/void asyncExecute(Long spuId);}

实现类:

@Service
public class GoodsHtmlServiceImpl implements GoodsHtmlService {@Autowiredprivate GoodsService goodsService;@Autowiredprivate TemplateEngine templateEngine;private final static Logger LOGGER = LoggerFactory.getLogger(GoodsHtmlServiceImpl.class);/*** 实现商品详情页静态化** @param spuId*/@Overridepublic void createHtml(Long spuId) {PrintWriter printWriter = null;try {//需要页面静态化的数据Map<String, Object> map = this.goodsService.loadData(spuId);//创建上下文Context context = new Context();context.setVariables(map);//文件输出File file = new File("D:\\JavaEnvironment\\Nginx\\nginx-1.20.1\\html\\item\\" + spuId + ".html");printWriter = new PrintWriter(file);//模板引擎执行静态化this.templateEngine.process("item",context,printWriter);} catch (FileNotFoundException e) {e.printStackTrace();}finally {if (printWriter != null){printWriter.close();}}}/*** 新建线程处理页面静态化* @param spuId*/@Overridepublic void asyncExecute(Long spuId){ThreadUtils.execute(() -> createHtml(spuId));}
}

线程类:

public class ThreadUtils {/*** 自定义一个线程池*/private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 5, 2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());/*** 提交任务* @param runnable*/public static void execute(Runnable runnable){poolExecutor.submit(runnable);}}

2.6、什么时候创建静态文件

编写好了创建静态文件的service,那么问题来了:什么时候去调用它呢

场景描述:

假如大部分的商品都有了静态页面。那么用户的请求都会被nginx拦截下来,根本不会到达leyou-goods-web服务。只有那些还没有页面的请求,才可能会到达这里。

因此,如果请求到达了这里,除了返回页面视图外,还应该创建一个静态页面,那么下次就不会再来访问leyou-goods-web服务了。

所以,在GoodsController中添加逻辑,去生成静态html文件:

注意:生成html 的代码不能对用户请求产生影响,所以这里使用额外的线程进行异步创建。

重启测试

访问一个商品详情,然后查看nginx目录:

2.7、nginx代理静态页面

修改nginx,让它对商品请求进行监听,指向本地静态页面,如果本地没找到,才进行反向代理:

server {listen       80;server_name  www.leyou.com;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Server $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;location /item {# 先找本地root html;if (!-f $request_filename) { #请求的文件不存在,就反向代理proxy_pass http://127.0.0.1:8084;break;}proxy_connect_timeout 600;proxy_read_timeout 600;}location / {proxy_pass http://127.0.0.1:9002;proxy_connect_timeout 600;proxy_read_timeout 600;}
}

重启测试,可以打开浏览器发现读取速度得到很大的提升

乐优商城(09)--商品详情相关推荐

  1. 乐优商城day13(商品详情页,rabbitMQ安装)

    所有代码发布在 [https://github.com/hades0525/leyou] Day13(rabbitmq) 2019年2月13日 14:45 使用thymeleaf thymeleaf基 ...

  2. 乐优商城 Day 09(thymeleaf,Rabbitmq,商品详情页,非教程)

    乐优商城学习Day09: 注意:此次代码都是在第八天的基础上 第八天的链接如下: https://blog.csdn.net/zcylxzyh/article/details/100859210 此次 ...

  3. 乐优商城笔记六:商品详情页

    使用模板引擎 Thymeleaf + nginx 完成商品详情页静态化 完成乐优商城商品详情页 搭建商品详情页微服务 创建子工程 GroupId:com.leyou.service ArtifactI ...

  4. 【javaWeb微服务架构项目——乐优商城day05】——商品规格参数管理(增、删、改,查已完成),SPU和SKU数据结构,商品查询

    乐优商城day05 0.学习目标 1.商品规格数据结构 1.1.SPU和SKU 1.2.数据库设计分析 1.2.1.思考并发现问题 1.2.2.分析规格参数 1.2.3.SKU的特有属性 1.2.4. ...

  5. 乐优商城(05)--商品管理

    乐优商城(05)–商品管理 一.导入图片资源 现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中: 将images.zip文件上传至/leyou/static目录: ...

  6. 乐优商城(四)商品规格管理

    文章目录 1. 商品规格 1.1 SPU 和 SKU 1.2 分析商品规格的关系 1.3 数据库设计 1.3.1 商品规格组表 1.3.2 商品规格参数表 2. 商品规格组 2.1 商品规格组前端 2 ...

  7. 乐优商城学习笔记五-商品规格管理

    0.学习目标 了解商品规格数据结构设计思路 实现商品规格查询 了解SPU和SKU数据结构设计思路 实现商品查询 了解商品新增的页面实现 独立编写商品新增后台功能 1.商品规格数据结构 乐优商城是一个全 ...

  8. 【javaWeb微服务架构项目——乐优商城day15】——会调用订单系统接口,实现订单结算功能,实现微信支付功能

    0.学习目标 会调用订单系统接口 实现订单结算功能 实现微信支付功能 源码笔记及资料: 链接:https://pan.baidu.com/s/1_opfL63P1pzH3rzLnbFiNw 提取码:v ...

  9. 乐优商城(10)--数据同步

    乐优商城(10)–数据同步 一.RabbitMQ 1.1.问题分析 目前已经完成了商品详情和搜索系统的开发.思考一下,是否存在问题? 商品的原始数据保存在数据库中,增删改查都在数据库中完成. 搜索服务 ...

  10. 学习乐优商城中遇到的坑

    本人暑假期间学习了乐优商城这个项目,历时二十天,遇到了许多的麻烦,应该会有挺多人遇到.所以想记录一下,让大家跳坑. 首先,我建议jar包的版本,都选择和老师的一样,不然会出现一些莫名奇妙的报错. 在本 ...

最新文章

  1. Centos和Redhat的区别和联系
  2. 1.Spring Security 详细简绍与入门
  3. Generative Adversarial Text to Image Synthesis --- 根据文字描述生成对应的图片
  4. 获得诺贝尔奖的底层小职员 | 从来没有一个高手,是在一夜之间强大起来的
  5. windbg调试masm生成程序的方法
  6. Android 三方库okhttp、gson、glide的使用
  7. java 时间生成字符串_java随机生成时间字符串的方法
  8. SQLServer 阻塞的等待类型解析
  9. mybatis学习(50):嵌套查询
  10. mysql建表语句增加注释_MySQL建表语句+添加注释
  11. php怎么取request,PHP-如何在Guzzle中获取Request对象?
  12. mysql 删除 like_MySQL 定时删除数据
  13. 计算机基础ppt_为什么大学老师教编程上课都不敲代码?为何老师上课都是PPT讲解,实战呢?...
  14. 计算机组成原理课程设计(附完整项目)
  15. 【js 分页】js 分页 方法一 【实用】
  16. EXCEL常用函数的操作及使用技巧(上篇)
  17. 另辟蹊径 直取通州的“墨迹天气”APP应用的成功故事
  18. 微信小程序base64实现小程序码
  19. 2进制 16进制 计算机术语,十六进制转二进制计算器
  20. [noip2016]天天爱跑步

热门文章

  1. 统计通话次数和时间的软件_通话时间统计app下载-通话时间统计下载v2.3-西西软件下载...
  2. html页面小宠物代码大全,宠物店网页设计html代码
  3. Android调用长截屏,Android实现长截屏功能
  4. 算法导论第三版 第1章习题答案
  5. 数字IC设计工程师笔试面试经典题
  6. 细等线体cass_CAD中,刚打开一个文件,细等线体显示不出来,然后换个字体后在换回细等线体才正常显示,怎么回事?...
  7. solr mysql原理_solr replication原理探究
  8. hosts文件作用及如何修改hosts文件
  9. 修改Hosts不生效的解决办法
  10. 项目日报模板_中山首个地下综合管廊项目取得重大进展