一,nginx动静分离
第1步:通过SwitchHosts新增二级域名:images.zmall.com
第2步:将本次项目的易买网所有静态资源js/css/images复制到nginx中的html目录下
第3步:在nginx的核心配置文件nginx.conf中新增二级域名images.zmall.com访问映射,用于实现nginx动静分离
注意:修改成功之后,重启nginx服务使其配置生效!!!
当我们访问80端口 或者images.zmall.com时就会自动路由到nginx中html文件中加载静态资源
server{
listen 80;
server_name images.zmall.com;
location / {
root html;
index index.html;
}
}
检测静态资源服务器配置成功
images.zmall.com/css/style.css
第4步:删除zmall-product商品服务和zmall-gateway网关服务下的static静态资源,改用nginx中配置的静态资源
第5步:修改zmall-product商品服务中的templates/common/head.html
<#assign ctx>
<#--域名,动态请求时需加入该前缀-->
http://product.zmall.com
</#assign>
<#--采用H5方式的base标签,在整个页面的url地址前加入,用于访问nginx中的静态资源-->
<base href="http://images.zmall.com/"/>
添加二级域名
第6步:分别重启zmall-product、zmall-gateway以及nginx后输入请求地址(启动nacos):
http://zmall.com/product-serv/index.html 走网关的形式
http://product.zmall.com/index.html 走nginx的形式
访问商品服务首页,如下所示:
如果出现IIS7,那么cmd窗口中执行下列指令
net stop w3svc
实现的原理解释:
二,服务调用
创建配置zmall-cart购物车模块
第1步:基于Spring initializr创建zmall-cart购物车模块
第2步:将zmall-order订单模块配置到主模块中
<modules>
...
<module>zmall-cart</module>
...
</modules>
第3步:修改pom.xml
<parent>
<groupId>com.zking.zmall</groupId>
<artifactId>zmall</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>zmall-cart</artifactId>
<dependencies>
<dependency>
<groupId>com.zking.zmall</groupId>
<artifactId>zmall-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
第4步:配置application.yml(端口:8030)
server:
port: 8030
spring:
application:
name: zmall-cart
datasource:
#type连接池类型 DBCP,C3P0,Hikari,Druid,默认为Hikari
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/zmall?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
username: root
password: 1234
freemarker:
suffix: .html
template-loader-path: classpath:/templates/
cloud:
nacos:
config:
server-addr: localhost:8848
#mybatis-plus配置
mybatis-plus:
#所对应的 XML 文件位置
mapper-locations: classpath*:/mapper/*Mapper.xml
#别名包扫描路径
type-aliases-package: com.zking.zmall.model
configuration:
#驼峰命名规则
map-underscore-to-camel-case: true
#日志配置
logging:
level:
com.zking.zmall.mapper: debug
第5步:在启动类上加入@EnableDiscoveryClient
第6步:分别将购物车页面和common/head.html导入到templates目录,并修改head.html中的ctx局部变量
<#assign ctx>
<#--一级域名,动态请求时需加入该前缀-->
http://cart.zmall.com
</#assign>
<#--采用H5方式的base标签,在整个页面的url地址前加入,用于访问nginx中的静态资源-->
<base href="http://images.zmall.com/"/>
第7步:在zmall-gateway网关服务中配置购物车的路由转发规则(重启gateway网关服务)
spring:
application:
name: zmall-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
...
- id: cart_route
uri: lb://zmall-cart # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
predicates:
- Path=/cart-serv/**
filters:
- StripPrefix=1
#此过滤器设置路由过滤器检查的请求属性,以确定是否应发送原始主机头,而不是由 HTTP 客户端确定的主机头
- PreserveHostHeader
注意:这里要配置过滤器PreserveHostHeader,用于处理重定向时依然已原始主机头发送请求。
第8步:创建CartController并定义请求方法
@Controller
public class CartController {
@RequestMapping("/cart.html")
public String toCart(){
return "buyCar";
}
@RequestMapping("/addCart")
public String addCart(Integer pid,Integer num){
return "redirect:/cart.html";
}
}
注意:这里使用redirect重定向方式跳转页面,在SpringCloud gateway路由转发过程中会导致域名跳转变成了http请求方式,所以必须在Gateway网关服务中进行相关的配置。具体请参考第8步的gateway网关路由配置。
<td><a href="http://cart.zmall.com/addCart?pid=${(product.id)!}&num=3" class="b_sure">去购物车结算</a><a href="#" class="b_buy">继续购物</a></td>
创建配置zmall-order订单模块
在zmall-user中通过openfeign方式访问order服务接口
定义openfeign接口
@FeignClient("zmall-order")
public interface IOrderFeignService {
@RequestMapping("/orderUserList")
List<Order> orderUserList();
}
启动类上设置@EnableDiscoveryClient和@EnableFeignClients
调用接口并测试接口
@Controller
public class UserController {
@Autowired
private IOrderFeignService orderFeignService;
@RequestMapping("/login.html")
public String toLogin(){
return "login";
}
@RequestMapping("/order.html")
@ResponseBody
public List<Order> orderUserList(){
return orderFeignService.orderUserList();
}
}
测试链路
http://product.zmall.com/index.html
http://product.zmall.com/product.html?pid=733
http://cart.zmall.com/cart.html
界面能够熊 商品微服务 直接跳转到 购物车微服务中
1.
2.
3.
http://localhost:8010/order.html
调用 用户微服务接口 ,能访问 订单微服务的数据
三,spring session实践
什么是Spring Session
SpringBoot整合Spring-Session的自动配置可谓是开箱即用,极其简洁和方便。这篇文章即介绍SpringBoot整合Spring-Session,这里只介绍基于RedisSession的实战。
Spring Session 是Spring家族中的一个子项目,Spring Session提供了用于管理用户会话信息的API和实现。它把servlet容器实现的httpSession替换为spring-session,专注于解决 session管理问题,默认Session信息存储在Redis中,可简单快速且无缝的集成到我们的应用中
spring session官网地址: https://spring.io/projects/spring-session
Spring Session的特性:
提供用户session管理的API和实现
提供HttpSession,以中立的方式取代web容器的session,比如tomcat中的session
支持集群的session处理,不必绑定到具体的web容器去解决集群下的session共享问题
为什么要使用Spring Session
SpringCloud微服务将一个完整的单体应用拆解成了一个个独立的子服务,而每一个独立的微服务子模块都将部署到不同的服务器中,而服务与服务之间是独立隔离的,这个时候使用要实现服务与服务之间的session会话共享,则需要借助于spring-session框架来解决分布式session管理与共享问题。
错误案例展示
在用户服务zmall-user中编写登录控制器,登录时创建session,并将当前登录用户存储sesion中。
@Controller
public class UserController {
@RequestMapping("/login.html")
public String toLogin(HttpSession session){
session.setAttribute("username","admin");
return "login";
}
}
在Gateway网关服务中添加用户服务的路由转发规则
spring:
application:
name: zmall-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
...
- id: user_route
uri: lb://zmall-user # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
predicates:
- Path=/user-serv/**
filters:
- StripPrefix=1
- PreserveHostHeader
在商品服务zmall-product中编写查询控制器,在登录创建session后,使用将sessionId置于cookie中访问。如果没有session将返回错误。
@Controller
public class ProductController {
@RequestMapping("/index.html")
public String index(Model model, HttpSession session){
Object username = session.getAttribute("username");
System.out.println("**********"+username);
return "index";
}
}
测试链路
#1.session信息存储
http://localhost:8010/login.html
#2.session信息获取
http://product.zmall.com/index.html
配置spring-session
在公共模块zmall-common中引入spring-session的pom配置,由于spring-boot包含spring-session的starter模块,所以pom中依赖:注意:公共模块作为所有微服务子模块的依赖支持,如果不在各服务模块中配置redis支持,会导致启动其他微服务时出现报错情况。
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring session-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!--commons-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
分别在商品服务zmall-product和用户服务zmall-user中配置application.yml
spring:
session:
redis:
flush-mode: on_save
namespace: session.zmall
cleanup-cron: 0 * * * * *
store-type: redis
timeout: 1800
redis:
host: localhost
port: 6379
password: 123456
jedis:
pool:
max-active: 100
max-wait: 10
max-idle: 10
min-idle: 10
database: 0
重新启动zmall-user和zmall-product服务,先访问:http://zmall.com/user-serv/login.html,然后在访问:http://zmall.com/product-serv/index.html;回到zmall-product模块控制台查看session获取情况。
注意:借助网关微服务内部可以访问,不同二级域名之间不可以访问
四,二级域名问题
测试在用户模块中保存用户信息,然后在产品模块中读取,但是使用二级域名方式访问读取失败
请分别在用户服务和商品服务中该配置类,解决二级域名访问session无效问题。
@Configuration
public class SessionConfig {
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("zmall.com");
cookieSerializer.setCookieName("ZMALLSESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
测试链路
#1.先访问
http://user.zmall.com/login.html
#2.后访问
http://product.zmall.com/index.html
五,用户登录
第1步:在zmall-common公共模块中创建全局异常处理、响应封装类
第2步:在zmall-user模块中定义IUserService及UserServiceImpl
IUserService
public interface IUserService extends IService<User> {
JsonResponseBody<?> userLogin(UserVo user, HttpServletRequest req, HttpServletResponse resp);
}
UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public JsonResponseBody<?> userLogin(UserVo user,
HttpServletRequest req,
HttpServletResponse resp) {
//1.判断用户账号和密码是否为空
if(StringUtils.isEmpty(user.getLoginName())||
StringUtils.isEmpty(user.getPassword()))
return new JsonResponseBody<>(JsonResponseStatus.USERNAME_OR_PWD_EMPTY);
//2.根据用户名查询数据对应的用户信息
User us = this.getOne(new QueryWrapper<User>()
.eq("loginName", user.getLoginName()));
//3.判断us用户对象是否为空
if(null==us)
return new JsonResponseBody<>(JsonResponseStatus.USERNAME_ERROR);
try {
//MD5加密转换处理
String pwd=MD5Utils.md5Hex(user.getPassword().getBytes());
//4.判断输入密码与数据库表存储密码是否一致
if(!us.getPassword().equals(pwd)){
return new JsonResponseBody<>(JsonResponseStatus.PASSWORD_ERROR);
}
} catch (Exception e) {
e.printStackTrace();
return new JsonResponseBody<>(JsonResponseStatus.ERROR);
}
//5.通过UUID生成token令牌并保存到cookie中
String token= UUID.randomUUID().toString().replace("-","");
//将随机生成的Token令牌保存到Cookie中,并设置1800秒超时时间
CookieUtils.setCookie(req,resp,"token",token,7200);
//6.将token令牌与spring session进行绑定并存入redis中
HttpSession session = req.getSession();
session.setAttribute(token,us);
return new JsonResponseBody<>(token);
}
}
第3步:创建UserVo类
@Data
public class UserVo {
private String loginName;
private String password;
}
第4步:在UserController中定义用户登录方法
/**
* 用户登陆功能实现
* @return
*/
@RequestMapping("/userLogin")
@ResponseBody
public JsonResponseBody<?> userLogin(UserVo user,
HttpServletRequest req,
HttpServletResponse resp){
return userService.userLogin(user,req,resp);
}
第5步:在前端login.html页面中定义登录js方法
<script>
$(function(){
$('.log_btn').click(function(){
let loginName=$('.l_user').val();
let password=$('.l_pwd').val();
if(''===loginName){
alert('请输入用户名!');
return false;
}
if(''===password){
alert('请输入密码!');
return false;
}
console.log({
loginName:loginName,
password:password
});
$.post('http://zmall.com/user-serv/userLogin',{
loginName:loginName,
password:password
},function(rs){
console.log(rs);
if(rs.code===200){
location.href='http://zmall.com/product-serv/index.html';
}else{
alert(rs.msg);
}
},'json');
});
});
</script>
登录成功后,跳转到商品首页
http://user.zmall.com/login.html
如果用二级域名进行测试,那么需要解决跨域问题;
url: jdbc:mysql://localhost:3306/zmall?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
成功了!