分布式电商项目(九)--Dubbo&Zookeeper&项目重构

禾白少二

SOA思想

SOA思想介绍

面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
在这里插入图片描述

RPC介绍(调用形式的统称)

RPC介绍

RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
远程过程调用:addAge方法在其他的服务器中,如果需要调用则必须通过远程的方式通知其他服务器帮我完成业务调用.

总结: 利用第三方的服务器,帮我完成业务调用的过程.
理解: 分布式环境中 业务调用几乎都是RPC的.

微服务

什么是微服务

说明:

  1. 为了降低代码的耦合性,将项目进行了拆分.按照功能模块拆分为若干个项目.该项目称之为服务.(分布式思想).
  2. 如果采用微服务的结构,要求服务器如果出现了故障应该实现自动化的故障的迁移(高可用HA)

现有服务分析

说明:由于nginx负载均衡/反向代理都需要人为的配置,并且出现了问题不能及时的实现故障的迁移,所以需要升级为微服务的架构的设计.
在这里插入图片描述

微服务架构设计

在这里插入图片描述
实现步骤:

  1. 服务提供者启动时,.将自己的信息注册到注册中心中.
  2. 注册中心接受到了用户的请求之后,更新服务列表信息.
  3. 当消费者启动时,首先会链接注册中心,获取服务列表数据.
  4. 注册中心将自己的服务列表信息同步给客户端(消费者)
  5. 消费者接收到服务列表数据之后,将信息保存到自己的本地.方便下次调用
  6. 当消费者接收到用户的请求时,根据自己服务列表的信息进行负载均衡的操作,选择其中一个服务的提供者,根据IP:PORT 进行RPC调用.
  7. 当服务提供者宕机时,注册中心会有心跳检测机制,如果检查宕机,则更新本地的服务列表数据,并且全网广播通知所有的消费者更新服务列表.

Zookeeper

Zookeeper介绍

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在zookeeper-3.4.3srcrecipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。

总结:Zookeeper负责服务的协调调度.当客户端发起请求时,返回正确的服务器地址.

Zookeeper下载

网址: http://zookeeper.apache.org/releases.html.

ZK安装

先安装JDK(jdk1.8)

1.上传安装文件

1.png
解压目录:

tar -xvf zookeeper-3.4.8.tar.gz

2.修改配置文件

在zk根目录下创建文件夹data/log

mkdir data log

2.png

3.跳入conf目录中修改配置文件

复制配置文件并且修改名称

cp zoo_sample.cfg zoo.cfg

3.png

4.启动zk

跳转到bin目录中 zk启动关闭命令如下.

sh zkServer.sh start 或者 ./zkServer.sh start
sh zkServer.sh stop
sh zkServer.sh status

4.png

Zookeeper集群安装

1.准备文件夹

在zookeeper根目录中创建新的文件夹zkCluster.
1.png
创建zk1/zk2/zk3文件夹.
2.png
在每个文件夹里创建data/log文件夹.

mkdir {zk1,zk2,zk3}/{data,log}

3.png

2.在每个文件夹里创建data/log文件夹.

mkdir {zk1,zk2,zk3}/{data,log}

4.png

3.分别在zk1/zk2/zk3中的data文件夹中创建新的文件myid.其中的内容依次为1/2/3,与zk节点号对应.

2020-11-18_195924.png

4.在config目录中将zoo_sample.cfg 复制为zoo1.cfg之后修改配置文件.

将zoo_sample.cfg 复制为zoo1.cfg之后修改配置文件.
6.png

5.修改zoo1.cfg

2020-11-18_200155.png

配置完成后将zoo1.cfg复制2份.之后需要修改对应的文件夹目录.和不同的端口即可.

6.ZK集群测试

通过下面的命令启动zk集群.

sh zkServer.sh start zoo1.cfg
sh zkServer.sh stop  zoo1.cfg
sh zkServer.sh status zoo1.cfg

检查主从关系,从机情况说明
8.png
检查主从关系,主机情况说明
9.png

关于zookeeper集群说明

Zookeeper集群中leader负责监控集群状态,follower主要负责客户端链接获取服务列表信息.同时参与投票.

为什么集群一般都是奇数个?

公式: 存活的节点 > N/2

公式解读:(zookeeper集群服务器数 - 宕机的服务器数量)>集群数/2
集群的概念即实现高可用,如果宕机一台就失效也不能称为集群,所以判断集群的依据是当有一台服务器宕机 公式依然成立即为集群
允许最大宕机数:保持公式成立的集群数与宕机数,否则集群崩溃

常识: 最小的集群的单位3台.
例子:
1个节点能否搭建集群? 1-1 > 1/2 假的 1个节点不能搭建集群
2个节点能否搭建集群? 2-1 > 2/2 假的 2个节点不能搭建集群
3个节点能否搭建集群? 3-1 > 3/2 真的 3个节点能搭建集群
4个节点能否搭建集群? 4-1 > 4/2 真的 4个节点能搭建集群

3个节点最多允许宕机1台,否则集群崩溃.
4个节点最多允许宕机1台,否则集群崩溃.

搭建奇数台和偶数台其实都可以,但是从容灾性的角度考虑,发现奇数和偶数的效果相同,.所以搭建奇数台.

ZK集群选举规则

说明: zk集群选举采用最大值(myid)优先的算法实现,如果集群中没有主机,则开始选举(超半数即可),如果有主机,则选举结束.
考题: 1 2 3 4 5 6 7 依次启动时
问题1:谁当主机? 4当主机
问题2:谁永远不能当选主机? 1,2,3

Dubbo框架

Dubbo介绍

Apache Dubbo |ˈdʌbəʊ| 提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。
在这里插入图片描述
调用原理图:
在这里插入图片描述

Dubbo入门案例

修改配置文件内容

1).修改SpringBoot的版本
在这里插入图片描述
2).修改模块名称 改为dubbo-jt-demo-interface
在这里插入图片描述

导入maven项目

在这里插入图片描述
修改俩个生产者的驱动为com.mysql.cj.jdbc.Driver
2020-11-18_201617.png

测试效果

在这里插入图片描述

创建接口

1).定义接口
在这里插入图片描述
2).定义接口代码
在这里插入图片描述

创建服务生产者

定义生产者的实现类

package com.jt.dubbo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.dubbo.mapper.UserMapper;
import com.jt.dubbo.pojo.User;
@Service(timeout=3000)    //3秒超时 内部实现了rpc
//@org.springframework.stereotype.Service//将对象交给spring容器管理
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public List<User> findAll() {
        
        System.out.println("我是第一个服务的提供者");
        return userMapper.selectList(null);
    }
    
    @Override
    public void saveUser(User user) {
        
        userMapper.insert(user);
    }
}

提供者配置文件

server:
  port: 9000   #定义端口

spring:
  datasource:
    #引入druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

#关于Dubbo配置   
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径
  application:              #应用名称
    name: provider-user     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复.

      
mybatis-plus:
  type-aliases-package: com.jt.dubbo.pojo       #配置别名包路径
  mapper-locations: classpath:/mybatis/mappers/*.xml  #添加mapper映射文件
  configuration:
    map-underscore-to-camel-case: true                #开启驼峰映射规则

服务消费者

编辑Controller

package com.jt.dubbo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.dubbo.pojo.User;
import com.jt.dubbo.service.UserService;

@RestController
public class UserController {
    
    //利用dubbo的方式为接口创建代理对象 利用rpc调用
    //@Reference(loadbalance="random") //负载均衡的随机算法(如果不写参数,默认也是random)
    //@Reference(loadbalance="roundrobin") //轮循算法
    //@Reference(loadbalance="consistenthash") //一致性hash的算法(参数全小写)
    //调用远程服务就像调用自己的服务一样的简单!!!
    @Reference(loadbalance="leastactive") //挑选压力最小的服务器
    private UserService userService; 
    
    /**
     * Dubbo框架调用特点:远程RPC调用就像调用自己本地服务一样简单
     * @return
     */
    @RequestMapping("/findAll")
    public List<User> findAll(){
        
        //远程调用时传递的对象数据必须序列化.
        return userService.findAll();
    }
    
    @RequestMapping("/saveUser/{name}/{age}/{sex}")
    public String saveUser(User user) {
        
        userService.saveUser(user);
        return "用户入库成功!!!";
    }
}

编辑YML配置文件

server:
  port: 9001
dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-user   #定义消费者名称
  registry:               #注册中心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.12

消费者测试

在这里插入图片描述

Dubbo高可用测试

测试需求

1).测试当服务器宕机,用户访问是否受影响. 用户访问不受影响. zk心跳检测机制
2).测试当zk集群宕机,用户访问是否受影响. 不受影响 消费者在本地有服务列表数据,自己维护.
3).测试是否有负载均衡的效果 用户访问有负载均衡的效果

Dubbo负载均衡

负载均衡方式

1.服务端负载均衡(集中式负载均衡)
说明: 用户访问服务器时不清楚真实的服务器到底是谁,由负载均衡服务器动态动态管理.
典型代表: NGINX
一般nginx服务器做反向代理使用,负载均衡只是提供的功能.

2.客户端负载均衡
说明:采用微服务架构时,当消费者访问服务提供者时,由于框架内部已经实现了负载均衡的策略,所以消费者访问提供者时已经完成了负载均衡的机制.所以将所有的压力平衡到了各个消费者中.
在这里插入图片描述

负载均衡-随机算法

默认条件下就是随机算法
在这里插入图片描述

在这里插入图片描述

负载均衡-轮询算法

在这里插入图片描述
在这里插入图片描述

负载均衡-一致性hash

在这里插入图片描述
在这里插入图片描述

负载均衡-最少访问

在这里插入图片描述
在这里插入图片描述

重构京淘项目

导入jar包

 <!--引入dubbo配置 -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

重构接口项目

说明:在jt-common中添加接口文件.
在这里插入图片描述

重构JT-SSO(生产者)

编辑Service实现类

在这里插入图片描述

编辑YML配置文件

server:
  port: 8093
  servlet:
    context-path: /    #在根目录中发布  缺省值.
spring:
  datasource:
    #引入druid数据源
    #type: com.alibaba.druid.pool.DruidDataSource
    #driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level: 
    com.jt.mapper: debug

#关于Dubbo配置
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径
  application:              #应用名称
    name: provider-user     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复. 

重构服务消费者

编辑UserController

在这里插入图片描述

编辑YML配置文件

server:
  port: 8092    
spring:     #定义springmvc视图解析器
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
      
#配置dubbo消费者
dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-user   #定义消费者名称
  registry:               #注册中心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183 

用户模块实现

用户注册实现

页面分析

1).页面url地址
在这里插入图片描述
2.页面提交参数
在这里插入图片描述
3.页面JS分析
在这里插入图片描述

编辑UserController

 /**
     * 完成用户的注册操作
     * url地址: http://www.jt.com/user/doRegister
     *          Request Method: POST
     * 请求参数:
     *          password: admin123
     *          username: admin123123123
     *          phone: 13111112225
     * 返回值类型:
     *          SysResult对象
     */
    @RequestMapping("/doRegister")
    @ResponseBody
    public SysResult saveUser(User user){

        //利用dubbo进行RPC调用
        dubboUserService.saveUser(user);
        return SysResult.success();
    }

编辑UserService

package com.jt.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;

@Service(timeout = 3000)
public class DubboUserServiceImpl implements DubboUserService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public void saveUser(User user) {
        //密码采用md5方式进行加密处理
        String password = user.getPassword();
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setEmail(user.getPhone()).setPassword(md5Pass);
        userMapper.insert(user);
    }
}

页面效果展现

在这里插入图片描述

用户登录

单点登录业务实现

单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的.

在这里插入图片描述
实现步骤:
1.用户输入用户名和密码之后点击登录按钮开始进行登录操作.
2.JT-WEB向JT-SSO发送请求,完成数据校验
3.当JT-SSO获取数据信息之后,完成用户的校验,如果校验通过则将用户信息转化为json.并且动态生成UUID.将数据保存到redis中. 并且返回值uuid.
如果校验不存在时,直接返回"不存在"即可.
4.JT-SSO将数据返回给JT-WEB服务器.
5.如果登录成功,则将用户UUID保存到客户端的cookie中.

页面URL分析

1).url请求
在这里插入图片描述
2).url参数
在这里插入图片描述
3).页面JS分析
在这里插入图片描述

编辑UserController

 /**
     * 业务:完成用户登录操作
     * url地址: http://www.jt.com/user/doLogin?r=0.35842191622936337
     * 参数:
     *          username: admin123
     *          password: admin123456
     * 返回值:   SysResult对象
     *
     * 业务具体实现:
     *      1.校验用户名和密码是否正确
     *      2.判断返回值结果是否为null 用户名和密码有误 返回201状态码
     *      3.如果返回值结果不为null  uuid保存到cookie中即可.
     *
     *  Cookie知识介绍:
     *      1.cookie.setPath("/")  根目录有效
     *      url1:  www.jt.com/addUser
     *      url2:  www.jt.com/user/addUser
     *
     *      2. cookie.setDomain("域名地址");  cookie在哪个域名中共享
     *      例子1:   cookie.setDomain("www.jt.com");
     *               只有在www.jt.com的域名中有效
     *
     *               cookie.setDomain("jt.com");
     *               只有在jt.com结尾的域名中有效
     *
     */
    @RequestMapping("/doLogin")
    @ResponseBody
    public SysResult doLogin(User user, HttpServletResponse response){
        String uuid = dubboUserService.doLogin(user);
        if(StringUtils.isEmpty(uuid)){
            return SysResult.fail();
        }
        //将uuid保存到Cookie中
        Cookie cookie = new Cookie("JT_TICKET",uuid);
        cookie.setMaxAge(30*24*60*60);  //让cookie 30天有效
        cookie.setPath("/");            //cookie在哪个url路径生效
        cookie.setDomain("jt.com");     //设定cookie共享
        response.addCookie(cookie);

        return SysResult.success();
    }

编辑UserService

 /**
     * 1.根据用户名和密码查询后端服务器数据
     * 2.将密码加密处理
     * @param user
     * @return
     */
    @Override
    public String doLogin(User user) {
        String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(md5Pass);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);//u/p不能
        //根据对象中不为空的属性,充当where条件.
        User userDB = userMapper.selectOne(queryWrapper);
        if(userDB == null){
            //根据用户名和密码错误
            return null;
        }
        //开始进行单点登录业务操作
        String uuid = UUID.randomUUID()
                          .toString()
                          .replace("-", "");
        userDB.setPassword("123456你信不?");   //去除有效信息.
        String userJSON = ObjectMapperUtil.toJSON(userDB);
        jedisCluster.setex(uuid, 30*24*60*60, userJSON);

        return uuid;
    }

页面效果展现

在这里插入图片描述

用户信息回显

用户信息回显业务需求

思路: 用户通过TICKET信息,利用JSONP的方式动态获取远程的服务器数据信息.之后将数据返回之后 回显数据.

用户URL请求

在这里插入图片描述

页面JS分析

在这里插入图片描述

编辑JT-SSO的UserController

 /**
     * 业务说明:
     *   通过跨域请求方式,获取用户的JSON数据.
     *   1.url地址:  http://sso.jt.com/user/query/efd321aec0ca4cd6a319b49bd0bed2db?callback=jsonp1605775149414&_=1605775149460
     *   2.请求参数:  ticket信息
     *   3.返回值:   SysResult对象 (userJSON)
     *   需求: 通过ticket信息获取user JSON串
     */
    @RequestMapping("/query/{ticket}")
    public JSONPObject findUserByTicket(@PathVariable String ticket,String callback){

        String userJSON = jedisCluster.get(ticket);
        if(StringUtils.isEmpty(userJSON)){

            return new JSONPObject(callback, SysResult.fail());
        }else{
            return new JSONPObject(callback, SysResult.success(userJSON));
        }
    }

页面效果展现

在这里插入图片描述

用户登出操作

退出业务逻辑

当用户点击退出操作时,应该重定向到系统首页. 同时删除redis信息/Cookie信息.

编辑UserController

 /**
     * 完成用户退出操作
     * url地址:http://www.jt.com/user/logout.html
     * 参数:   没有参数
     * 返回值:  String 重定向到系统首页
     * 业务:
     *      1.删除redis   K-V   获取ticket信息
     *      2.删除cookie
     */
    @RequestMapping("/logout")
    public String logout(HttpServletRequest request,HttpServletResponse response){
        //1.获取Cookie中的JT_TICKET值
        Cookie[] cookies = request.getCookies();
        if(cookies != null && cookies.length>0){
            for (Cookie cookie : cookies){
                if(cookie.getName().equals("JT_TICKET")){
                    String ticket = cookie.getValue();
                    //redis删除ticket信息
                    jedisCluster.del(ticket);
                    cookie.setMaxAge(0);    //0表示立即删除
                    //规则cookie如果需要操作,必须严格定义
                    cookie.setPath("/");
                    cookie.setDomain("jt.com");
                    response.addCookie(cookie);
                }
            }
        }

        return "redirect:/";
    }

商品信息展现

业务需求说明

当用户点击商品时应该跳转到商品的展现页面,在页面中应该展现2部分数据.item数据/itemDesc数据. item.jsp页面
数据取值方式:
1.获取item信息 ${item.title }
2.获取ItemDesc数据 ${itemDesc.itemDesc}

重构JT-MANAGE

编辑ItemService

在这里插入图片描述

编辑YML配置

server:
  port: 8091
  servlet:
    context-path: /    #在根目录中发布  缺省值.
spring:
  datasource:
    #引入druid数据源
    #type: com.alibaba.druid.pool.DruidDataSource
    #driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level: 
    com.jt.mapper: debug

#配置manage Dubbo服务
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径
  application:              #应用名称
    name: provider-item     #一个接口对应一个服务名称
  registry:
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20881  #每一个服务都有自己特定的端口 不能重复.

实现页面跳转

页面URL分析

在这里插入图片描述

编辑ItemController

package com.jt.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.DubboItemService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.xml.ws.RequestWrapper;

@Controller
public class ItemController {

    @Reference(check = false)
    private DubboItemService itemService;

    /**
     * 实现商品的展现
     * url:     http://www.jt.com/items/562379.html
     * 参数:     562379商品ID号
     * 返回值:   item.jsp
     * 页面取值:    item对象/itemDesc对象
     *          {item.id/title}
     */
    @RequestMapping("/items/{itemId}")
    public String findItemById(@PathVariable Long itemId, Model model){

        Item item = itemService.findItemById(itemId);
        ItemDesc itemDesc = itemService.findItemDescById(itemId);
        model.addAttribute("item",item);
        model.addAttribute("itemDesc",itemDesc);
        return "item";
    }

}

编辑ItemService

package com.jt.web.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.mapper.ItemDescMapper;
import com.jt.mapper.ItemMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.jt.service.DubboItemService;
import org.springframework.beans.factory.annotation.Autowired;

@Service(timeout = 3000)
public class DubboItemServiceImpl implements DubboItemService {

    @Autowired
    private ItemMapper itemMapper;
    @Autowired
    private ItemDescMapper itemDescMapper;

    @Override
    public Item findItemById(Long itemId) {

        return itemMapper.selectById(itemId);
    }

    @Override
    public ItemDesc findItemDescById(Long itemId) {

        return itemDescMapper.selectById(itemId);
    }
}

页面效果展现

在这里插入图片描述

阅读 665
33 声望
22 粉丝
0 条评论
你知道吗?

33 声望
22 粉丝
文章目录
宣传栏