项目微服务的部署架构如下:
image.png
用户访问所有请求全部都是访问我们的nginx,nginx进行反向代理,将请求和数据转发给网关,网关再路由到各个服务。
thymeleaf官网 usingthymeleaf.pdf thymeleaf3.0.5中文参考手册.pdf

一、微服务整合thymeleaf实现页面渲染

1.1 引入依赖&修改配置&整合文件

首页相关页面是是属于商品模块的操作,所以现在给商品模块添加视图依赖。
直接在common下导入springboot-thymeleaf模板引擎的依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  4. </dependency>

引入相关资源:将首页资源中的静态资源(index)导入 static 目录, 将视图页面(index.html)导入 templates 目录。springboot默认会去找templates下的index.html文件。
image.png
配置文件配置关闭缓存:

  1. spring:
  2. thymeleaf:
  3. cache: false
  4. #prefix: classpath:/templates //配置前缀:类路径(resources文件夹就是类路径),默认就是classpath:/templates
  5. #suffix: .html // 配置后缀,默认.html

我们启动product项目:点击image.png,发现可以直接访问到index.html
image.png
静态资源放在了static文件加下,可以直接通过localhost:10000/静态资源路径,即可直接访问到静态资源。
模板引擎小结:

  1. 引入spring-boot-starter-thymeleaf依赖,配置关闭缓存;
  2. 静态资源都放在static文件夹下就可以按照路径直接访问;
  3. 页面放在templates文件夹下,直接访问
    1. SpringBoot访问项目的时候默认会找index.html
  4. 页面修改不重启服务器实时更新
    1. 引入dev-tools
    2. 修改完页面,使用ctrl+shift+F9重新自动编译下页面

规范:对于页面上的rest接口我们都放在app包下(controller包改名为app),所有controller我们放在web包下。

1.2 首页跳转—渲染分类数据

1.2.1 请求 /index.html 或 / 直接跳转至首页

  1. /**
  2. * 开发页面跳转功能
  3. * @author mrlinxi
  4. * @create 2022-03-19 0:28
  5. */
  6. @Controller
  7. public class IndexController {
  8. @Autowired
  9. CategoryService categoryService;
  10. @GetMapping(value = {"/index.html", "/"})
  11. public String indexPage(Model model) {
  12. // 查出我们所有的1级分类
  13. List<CategoryEntity> Level1Categories = categoryService.getLevel1Categories();
  14. // 这里不用加/templates 和.html
  15. // 因为SpringBoot配置了默认的prefix:classpath:/templates/和suffix:.html
  16. // 视图解析器进行拼串 classpath:/templates/ + 返回值 + .html
  17. model.addAttribute("categories", Level1Categories); // model中放的数据会放到请求域,默认是一个转发
  18. return "index";
  19. }
  20. }

1.2.2 给首页返回一级分类数据

引入spring-boot-devtools依赖,实时更新

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-devtools</artifactId>
  4. <optional>true</optional>
  5. </dependency>
  1. /**
  2. * 获取所有一级分类
  3. * @return
  4. */
  5. @Override
  6. public List<CategoryEntity> getLevel1Categories() {
  7. List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("cat_level", 1));
  8. return categoryEntities;
  9. }

1.2.3 首页展示二三级分类数据

创建二级分类Vo

  1. package com.atguigu.gulimall.gulimallproduct.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import java.util.List;
  6. /**
  7. * 二级分类vo
  8. */
  9. @Data
  10. @AllArgsConstructor
  11. @NoArgsConstructor
  12. public class Catelog2Vo {
  13. private String catalog1Id; //一级父分类id
  14. private List<Catelog3Vo> catalog3List; // 三级子分类id
  15. private String id;
  16. private String name;
  17. /**
  18. * 三级分类Vo内部类
  19. */
  20. @Data
  21. @AllArgsConstructor
  22. @NoArgsConstructor
  23. public static class Catelog3Vo {
  24. private String catalog2Id; //二级父分类id
  25. private String id;
  26. private String name;
  27. }
  28. }

在IndexController中实现cataLogLoader.js中发送的请求端口image.png

  1. // index/catalog.json
  2. @ResponseBody
  3. @GetMapping("/index/catalog.json")
  4. public Map<String, List<Catelog2Vo>> getCatalogJson() {
  5. Map<String, List<Catelog2Vo>> map = categoryService.getCatalogJson();
  6. return map;
  7. }
  1. /**
  2. * 将目录信息按前端数据格式封装返回
  3. * @return
  4. */
  5. @Override
  6. public Map<String, List<Catelog2Vo>> getCatalogJson() {
  7. // 1.查出所有1级分类
  8. List<CategoryEntity> level1Categories = getLevel1Categories();
  9. // 2.封装数据
  10. Map<String, List<Catelog2Vo>> collect = level1Categories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
  11. // 拿到每一个一级分类,查到当前一级分类的所有二级分类
  12. List<CategoryEntity> category2LevelEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()).eq("cat_level", 2));
  13. // 封装结果,我们需要封装二级分类的 父分类id,子分类3级分类的集合,二级分类Id和二级分类名
  14. List<Catelog2Vo> catelog2Vos = null;
  15. if (!CollectionUtils.isEmpty(category2LevelEntities)) {
  16. // 对每个二级分类进行封装
  17. catelog2Vos = category2LevelEntities.stream().map(level2 -> {
  18. // 找二级分类的三级分类
  19. List<CategoryEntity> category3LevelEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", level2.getCatId()));
  20. // 把当前二级分类对应的三级分类CategoryEntity封装成Catelog2Vo.Catelog3Vo
  21. List<Catelog2Vo.Catelog3Vo> catelog3VoList = null;
  22. if (!CollectionUtils.isEmpty(category3LevelEntities)) {
  23. catelog3VoList = category3LevelEntities.stream().map(level3 -> new Catelog2Vo.Catelog3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName())).collect(Collectors.toList());
  24. }
  25. Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), catelog3VoList, level2.getCatId().toString(), level2.getName());
  26. return catelog2Vo;
  27. }).collect(Collectors.toList());
  28. }
  29. return catelog2Vos;
  30. }));
  31. return collect;
  32. }

二、Nginx——搭建域名反向代理

nginx官方文档
我们不想暴露真实的ip和端口,通过Nginx的反向代理,即可实现。
image.png

2.1 修改windows host文件

host中可以做本地域名映射,需要对host文件进行修改。文件位置:C:\Windows\System32\drivers\etc
在host文件中添加如下映射: 虚拟机Ip 域名

# guli mall # 192.168.190.135 gulimall.com

image.png现在直接通过域名就可以访问到nginx(访问默认80端口,nginx就是80端口)

2.2 nginx配置

我们希望让nginx帮我们进行反向代理,所有来自原gulimall.com的请求,都转到商品服务。

2.2.1 nginx配置文件解析

nginx配置文件解析 我们将docker的nginx等文件挂载到了虚拟机,其中/mydata/nginx/conf/nginx.conf就是配置文件。配置文件主要分如下几个部分
全局块:主要配置用户组,worker process的数量、pid存档路径和日志存放路径等
Event块:主要设置每个worker process的最大连接数(默认1024),这部分的配置对 Nginx 的性能影响较大,在实际中应该灵活配置。
Http块:这算是 Nginx 服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里。需要注意的是:http 块也可以包括 http 全局块、server 块。
image.pngconf.d文件夹下有一个default.conf配置文件,这个就是一个server块
image.png

2.2.2 不转发到网关,直接转到商品服务

默认配置下,我们访问 gulimall.com 会请求 nginx 默认的 index 页面,现在我们要做的是当访问gulimall.com 的时候转发到我们的商品模块的商城首页界面。

查看windows ip

因为商品服务是在宿主机上运行,所以nginx转发的时候需要转发到宿主机,在cmd 中输入ipconfig即可查看宿主机Ip
image.png
这里192.168.56.1跟192.168.190.1都是宿主机的ip地址,我习惯用虚拟机那个网段,所以配置当访问nginx /请求时转发到192.168.190.1:10000 商品服务首页。
这里我本机通过192.168.190.1:10000访问时没有问题的,但是通过nginx做反向代理的时候只有192.168.56.1可以代理成功,另外一个一直超时,不知道是什么情况,所以最后用了190.168.56.1:10000

配置代理

直接copy一份默认配置,命名为gulimall.conf mv default.conf gulimall.conf这个就对应nginx.conf的server块。

server { listen 80; server_name gulimall.com; #监听来源于gulimall.com 地址 #charset koi8-r; #access_log /var/log/nginx/log/host.access.log main; location / { # 配置请求转发到192.168.56.1:10000 proxy_pass http://192.168.56.1:10000 } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location = /50x.html { root /usr/share/nginx/html; } }

2.2.3 负载均衡转发到网关

现在虽然完成了反向代理,但如果我们是分布式的情况下,我们商品服务的集群地址他是不一样的,我们不可能每次都去修改nginx的proxy_pass。所以我们选择nginx代理到网关,让网关转发请求到商品服务。(我想这里应该也可以直接通过nginx的负载均衡转发到商品服务的集群,但是不如网关方便)
思路:nginx负载均衡给网关,网关再负载均衡到对应的服务。(分布式中最重要的一点就是保证高可用,所以处处都要考虑集群)nginx负载均衡文档

http全局块中配置upstream

修改nginx.conf文件
image.png

server块负载均衡配置

nginx代理给网关的时候会丢失请求的host信息,所以不要忘记加上proxy_set_header Host $host;如果不加后面网关通过Host断言机制会失效。
image.png

gateway网关配置

现在我们访问gulimall.com发现404,这是因为网关没有配置路由规则,不知道路由到哪里。
image.png
注意,这里按照Host的断言一定要放到最后面,如果放在最前面,我们通过域名+/api/xxx等接口都会被屏蔽。因为网关会优先匹配Host,他会直接路由给我们商品服务,而商品服务中是没有/api这个请求路径的,需要过滤。

  1. # 配置nginx反向代理路由规则
  2. - id: gulimall_host_route
  3. uri: lb://gulimall-product # lb://服务名 进行负载均衡转发
  4. predicates:
  5. - Host=gulimall.com # 通过Host进行断言

nginx代理给网关的时候会丢失请求的host信息,所以在配置转发的时候,要设置添加Host信息。

整体示意图

image.png

最终域名映射目标

image.png