项目微服务的部署架构如下:
用户访问所有请求全部都是访问我们的nginx,nginx进行反向代理,将请求和数据转发给网关,网关再路由到各个服务。
thymeleaf官网 usingthymeleaf.pdf thymeleaf3.0.5中文参考手册.pdf
一、微服务整合thymeleaf实现页面渲染
1.1 引入依赖&修改配置&整合文件
首页相关页面是是属于商品模块的操作,所以现在给商品模块添加视图依赖。
直接在common下导入springboot-thymeleaf模板引擎的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入相关资源:将首页资源中的静态资源(index)导入 static 目录, 将视图页面(index.html)导入 templates 目录。springboot默认会去找templates下的index.html文件。
配置文件配置关闭缓存:
spring:
thymeleaf:
cache: false
#prefix: classpath:/templates //配置前缀:类路径(resources文件夹就是类路径),默认就是classpath:/templates
#suffix: .html // 配置后缀,默认.html
我们启动product项目:点击,发现可以直接访问到index.html
静态资源放在了static文件加下,可以直接通过localhost:10000/静态资源路径,即可直接访问到静态资源。
模板引擎小结:
- 引入spring-boot-starter-thymeleaf依赖,配置关闭缓存;
- 静态资源都放在static文件夹下就可以按照路径直接访问;
- 页面放在templates文件夹下,直接访问
- SpringBoot访问项目的时候默认会找index.html
- 页面修改不重启服务器实时更新
- 引入dev-tools
- 修改完页面,使用ctrl+shift+F9重新自动编译下页面
规范:对于页面上的rest接口我们都放在app包下(controller包改名为app),所有controller我们放在web包下。
1.2 首页跳转—渲染分类数据
1.2.1 请求 /index.html 或 / 直接跳转至首页
/**
* 开发页面跳转功能
* @author mrlinxi
* @create 2022-03-19 0:28
*/
@Controller
public class IndexController {
@Autowired
CategoryService categoryService;
@GetMapping(value = {"/index.html", "/"})
public String indexPage(Model model) {
// 查出我们所有的1级分类
List<CategoryEntity> Level1Categories = categoryService.getLevel1Categories();
// 这里不用加/templates 和.html
// 因为SpringBoot配置了默认的prefix:classpath:/templates/和suffix:.html
// 视图解析器进行拼串 classpath:/templates/ + 返回值 + .html
model.addAttribute("categories", Level1Categories); // model中放的数据会放到请求域,默认是一个转发
return "index";
}
}
1.2.2 给首页返回一级分类数据
引入spring-boot-devtools依赖,实时更新
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
/**
* 获取所有一级分类
* @return
*/
@Override
public List<CategoryEntity> getLevel1Categories() {
List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("cat_level", 1));
return categoryEntities;
}
1.2.3 首页展示二三级分类数据
创建二级分类Vo
package com.atguigu.gulimall.gulimallproduct.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 二级分类vo
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Catelog2Vo {
private String catalog1Id; //一级父分类id
private List<Catelog3Vo> catalog3List; // 三级子分类id
private String id;
private String name;
/**
* 三级分类Vo内部类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Catelog3Vo {
private String catalog2Id; //二级父分类id
private String id;
private String name;
}
}
在IndexController中实现cataLogLoader.js中发送的请求端口
// index/catalog.json
@ResponseBody
@GetMapping("/index/catalog.json")
public Map<String, List<Catelog2Vo>> getCatalogJson() {
Map<String, List<Catelog2Vo>> map = categoryService.getCatalogJson();
return map;
}
/**
* 将目录信息按前端数据格式封装返回
* @return
*/
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
// 1.查出所有1级分类
List<CategoryEntity> level1Categories = getLevel1Categories();
// 2.封装数据
Map<String, List<Catelog2Vo>> collect = level1Categories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
// 拿到每一个一级分类,查到当前一级分类的所有二级分类
List<CategoryEntity> category2LevelEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()).eq("cat_level", 2));
// 封装结果,我们需要封装二级分类的 父分类id,子分类3级分类的集合,二级分类Id和二级分类名
List<Catelog2Vo> catelog2Vos = null;
if (!CollectionUtils.isEmpty(category2LevelEntities)) {
// 对每个二级分类进行封装
catelog2Vos = category2LevelEntities.stream().map(level2 -> {
// 找二级分类的三级分类
List<CategoryEntity> category3LevelEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", level2.getCatId()));
// 把当前二级分类对应的三级分类CategoryEntity封装成Catelog2Vo.Catelog3Vo
List<Catelog2Vo.Catelog3Vo> catelog3VoList = null;
if (!CollectionUtils.isEmpty(category3LevelEntities)) {
catelog3VoList = category3LevelEntities.stream().map(level3 -> new Catelog2Vo.Catelog3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName())).collect(Collectors.toList());
}
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), catelog3VoList, level2.getCatId().toString(), level2.getName());
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
return collect;
}
二、Nginx——搭建域名反向代理
nginx官方文档
我们不想暴露真实的ip和端口,通过Nginx的反向代理,即可实现。
2.1 修改windows host文件
host中可以做本地域名映射,需要对host文件进行修改。文件位置:C:\Windows\System32\drivers\etc
在host文件中添加如下映射: 虚拟机Ip 域名
# guli mall # 192.168.190.135 gulimall.com
现在直接通过域名就可以访问到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 块。conf.d文件夹下有一个default.conf配置文件,这个就是一个server块
2.2.2 不转发到网关,直接转到商品服务
默认配置下,我们访问 gulimall.com 会请求 nginx 默认的 index 页面,现在我们要做的是当访问gulimall.com 的时候转发到我们的商品模块的商城首页界面。
查看windows ip
因为商品服务是在宿主机上运行,所以nginx转发的时候需要转发到宿主机,在cmd 中输入ipconfig即可查看宿主机Ip
这里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
server块负载均衡配置
nginx代理给网关的时候会丢失请求的host信息,所以不要忘记加上proxy_set_header Host $host;
如果不加后面网关通过Host断言机制会失效。
gateway网关配置
现在我们访问gulimall.com发现404,这是因为网关没有配置路由规则,不知道路由到哪里。
注意,这里按照Host的断言一定要放到最后面,如果放在最前面,我们通过域名+/api/xxx等接口都会被屏蔽。因为网关会优先匹配Host,他会直接路由给我们商品服务,而商品服务中是没有/api这个请求路径的,需要过滤。
# 配置nginx反向代理路由规则
- id: gulimall_host_route
uri: lb://gulimall-product # lb://服务名 进行负载均衡转发
predicates:
- Host=gulimall.com # 通过Host进行断言
nginx代理给网关的时候会丢失请求的host信息,所以在配置转发的时候,要设置添加Host信息。