springcloud微服务_05_redis优化和页面静态化

前言

商品模块博客中的商品类型进行优化。主要是使用Redis进行商品类型后台缓存优化模板Velocity进行商品类型主页面页面静态化

一、商品类型优化方案

1、为什么要进行优化

商品的品牌和类型,都是从数据库中全部获取出来的,每一次都要去数据库中查询一次,从而增加数据库的访问压力,反应时间就会长,造成用户体验差。

  • 后台管理(Redis缓存解决
    使用缓存解决对数据库的频繁操作问题,用内存查询替换数据库磁盘查询.
  • 商城主页(页面静态化解决
    上面的缓存还不够优化,如果高并发,就会访问Redis缓存N多次,那么就会对缓存服务器造成压力。所以以不发请求静态页面代替要发请求静态页面或者动态页面。没有对后台数据获取.

二、商品类型后台缓存优化。Redis

1、常见的缓存实现方案

在这里插入图片描述

(1)、二级缓存

JPA、mybatis的二级缓存,默认情况下不支持集群环境使用

(2)、Redis中央缓存

2、缓存中的数据和数据库中的数据之间的交互

缓存中数据是对数据库中数据的一种内存存放,如何保持两者的数据一致?换言之它们之间如何交互的

在这里插入图片描述

3、Redis的数据存储

在redis中:序列化和json字符串(采纳)

  • 存:
    就是把数据库的数据存到Redis中(json字符串)
  • 取:
    将Redis(json字符串)的数据转换成对象。
(1)、使用到的数据存储转换技术
  • Springmvc:jackson
  • Google:gson
  • Alibaba:fastjson技术 ===>最牛逼
(2)、以下博客是使用fastjson技术来进行对象和json字符串之间的相互转换集合和json字符串之间的相互转换

FastJson入门博客地址

4、项目实现redis

将像Redis缓存这样的简单服务封装到一个公共服务模块中去,因为项目不止商品模块要用到缓存,其他的模块也会用到。

(1)、创建公共的服务模块

先创建公共的服务模块的父模块aigou_common_parent,以及它的公共接口子模块aigou_common_interface和服务子模块aigou_common_service_6699
在这里插入图片描述

( 2)、在pom.xml中配置模块之间的依赖
1)、 服务子模块aigou_common_service_6699依赖于公共接口子模块aigou_common_interface

在这里插入图片描述

2)、公共接口子模块aigou_common_interface依赖于公共基础模块中的公共工具子模块aigou_basic_util

在这里插入图片描述

3)、然后经过上面的依赖配置后,服务子模块aigou_common_service_6699也就间接了依赖了公共基础模块中的公共工具子模块aigou_basic_util

在这里插入图片描述

(3)、公共接口子模块aigou_common_interface

feign的支持和velocity模板支持(在接下来的页面静态化需要)

负载均衡feign:服务内部之间调用使用负载均衡,可以在服务子模块aigou_common_service_6699中使用feign,但是为了规范,==将feign写在公共的服务模块的公共接口子模块中。==方便其他地方好用
在这里插入图片描述

1)、在公共接口子模块aigou_common_interface的pom.xml中引入需要的依赖
<dependencies>
    <dependency>
        <groupId>cn.lyq</groupId>
        <artifactId>aigou_basic_util</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <!--feign的支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <!--velocity页面静态化的支持-->
    <!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity -->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity</artifactId>
        <version>1.7</version>
    </dependency>
</dependencies>
2)、创建feign的客户端接口。RedisClient
/**
 * @author lyq
 * @date 2019/5/12 21:20
 * 对Redis设置和获取的操作接口
 */
//feign客户端注解
@FeignClient(value = "COMMON-PROVIDER",fallback = RedisFall.class)
public interface RedisClient {

    /**
     * post参数接受:因为set存值有很多参数,用post方式提交
     * @RequestParam:请求参数,从请求参数key中拿值设置到参数key中去
     * @param key
     * @param value
     * 设置。将参数以key/value的形式存入Redis中。
     */
    @RequestMapping(value = "/redis/set",method = RequestMethod.POST)
    void setRedis(@RequestParam("key")String key,@RequestParam("value") String value);

    /**
     * @param key
     * @return
     * 获取json字符串。传入个参数key键拿到Redis的json字符串
     */
    @RequestMapping(value = "/redis/get/{key}",method = RequestMethod.GET)
    String getRedis(@PathVariable("key") String key);
}
3)、创建feign的托底数据。RedisFall
/**
 * @author lyq
 * @date 2019/5/12 21:48
 * redis的托底数据。想写啥写啥,暂时没有写
 */
@Component//配成一个组件
public class RedisFall implements FallbackFactory<RedisClient> {


    public RedisClient create(Throwable throwable) {
        //要得到一个RedisClient对象,那么就new一个
        return new RedisClient() {
            public void setRedis(String key, String value) {

            }

            public String getRedis(String key) {
                return null;
            }
        };
    }
}
(4)、暴露出去的redis服务子模块aigou_common_service_6699

上面的公共接口子模块aigou_common_interface是不会暴露出去的,而要把服务子模块aigou_common_service_6699暴露出去。因为有接口要被使用

1)、在pom.xml中引入依赖

Redis的客户端jedis的支持、springboot支持、以及注册到注册中心支持

jedis操作Redis,类似于jdbc操作MySQL一个道理

<dependencies>
    <dependency>
        <groupId>cn.lyq</groupId>
        <artifactId>aigou_common_interface</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <!--
     redis服务:客户端==>jedis;
     fastjson:在业务工程中使用
   -->
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>

    <!--springboot支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <!--eureka客户端,服务消费者也要从注册中心获取可用服务列表-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>
2)、服务的controller类。RedisController

controller类要去实现feign的客户端接口RedisClient。因为要约束controller,要一致。它要对外暴露接口。
调用Redis的工具类RedisUtil,完成Redis的set和get方法

@RestController
@RequestMapping("/redis")
public class RedisController implements RedisClient {

    @Override
    @RequestMapping(value = "/set",method = RequestMethod.POST)
    public void setRedis(@RequestParam("key") String key, @RequestParam("value") String value) {
        //redis的set方法。调用工具类
        RedisUtil.set(key,value);
    }

    @Override
    @RequestMapping(value = "/get/{key}",method = RequestMethod.GET)
    public String getRedis(@PathVariable("key") String key) {
        //Redis的get方法。调用工具类
        return RedisUtil.get(key);
    }
}
3)、YAML配置。application.yml文件
server:
  port: 6699
  max-http-header-size: 4048576
spring:
  application:
    name: COMMON-PROVIDER #Redis的接口类会根据此名字扫描
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
4)、创建启动类

公共服务模块aigou_common_service创建个启动类
在这里插入图片描述

5)、创建个Redis的工具类。RedisUtil

由于只是在Redis的服务端使用得到,所以就将工具类放在自己的服务中aigou_common_service_6699
连接池的配置API的封装

/**
 * redis的工具类 
 */
public class RedisUtil {
    private static JedisPool jedisPool;
    static {
        //连接池的配置:
        GenericObjectPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(20);
        poolConfig.setMaxIdle(5);
        poolConfig.setMaxWaitMillis(3000);
        poolConfig.setTestOnBorrow(true);
        //创建pool:
        // (GenericObjectPoolConfig poolConfig, String host, int port, int timeout, String password)
         jedisPool = new JedisPool(poolConfig,"127.0.0.1",6379,2000,"123456");
    }

    //设置。set方法  将值设置到Redis中
    public  static void set(String key,String value){
        Jedis jedis =null;
        try {
            jedis= jedisPool.getResource();
            jedis.set(key,value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
    }

    //获取json字符串。get方法
    public  static String get(String key){
        Jedis jedis =null;
        try {
            jedis= jedisPool.getResource();
            return jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if(jedis!=null){
                jedis.close();
            }
        }
    }

    //测试Redis是否能用
    public static void main(String[] args) {
        RedisUtil.set("sex","1");
    }
}
6)、测试Redis工具类

在这里插入图片描述

(5)、Redis服务的消费者的业务逻辑

配置好了redis服务子模块aigou_common_service_6699,然后再来处理Redis的消费者。现在项目中的Redis的消费者就是商品分类和商品品牌

1)、在Redis服务的消费者 aigou_product_service子模块的pom.xml中引入feign的接口依赖,以及fastjson的依赖

使用feign来调用Redis的服务,那么消费者模块aigou_product_service子模块要注入Feign的接口,也就是要使用RedisClient接口。那么就要引入feign接口的依赖
fastjson:操作Redis数据的,能操作对象与json字符串之间的相互转换和list集合和json字符串之间的相互转换

<!--feign的调用的接口的依赖-->
<dependency>
    <groupId>cn.lyq</groupId>
    <artifactId>aigou_common_interface</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<!--fastjson的依赖-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>
2)、aigou_product_service子模块中商品类型的树状图实现Redis

商品模块的博客中已经完成了商品分类的树状图结构,但是没有将数据放入Redis缓存中去,每次都要去数据库查询,很影响性能,所以此处作为Redis的消费者去调用Redis的服务,实现Redis的缓存效果。

  • 商品类型的service实现类ProductTypeServiceImpl更改代码

做商品类型,从数据库中查询数据之前,先在Redis中查询是否有数据,如果没有那么就要将从数据库中查询出的数据存入到Redis中去,再返回去;如果第一次进来的Redis中就有数据,那么就直接返回。

@Service
public class ProductTypeServiceImpl extends ServiceImpl<ProductTypeMapper, ProductType> implements IProductTypeService {

    //注入mapper对象
    @Autowired
    private ProductTypeMapper productTypeMapper;

    //注入Redis接口
    @Autowired
    private RedisClient redisClient;

    //实现树状结构
    @Override
    public List<ProductType> treeData() {
        //递归方法。查看商品类型数据库表得知pid=0为最上一级。返回前台的都是一级菜单
        //return treeDataRecursion(0L);

        //循环方法。
        return treeDataLoop();
    }

    /**
     * 步骤:循环查询
     * 1:先查询出所有的数据
     * 2:再组装父子结构
     * @return
     */
    public List<ProductType> treeDataLoop() {
        //返回的一级菜单
        List<ProductType> result = new ArrayList<>();
        //1.先查询出所有的数据。selectList方法的参数mapper为null即可
        List<ProductType> productTypes=null;
        //List<ProductType> productTypes = productTypeMapper.selectList(null);
        //数据从Redis中获取
        //1.先通过key去Redis中获取json字符串
        String productTypeJson = redisClient.getRedis(AiGouConstants.COMMON_PRODUCT_TYPE);
        //1.1 Redis中没有这个key,那么就要从数据库去获取,并放入Redis中返回
        if(StringUtils.isEmpty(productTypeJson)){
            //Redis中没有数据
            //就从数据库中查询
            productTypes = productTypeMapper.selectList(null);
            //然后将数据存入到Redis中。使用fastJson技术
                //将list对象集合转换成json字符串
            String jsonString = JSON.toJSONString(productTypes);
                //将json字符串存入到Redis中。key/value
            redisClient.setRedis(AiGouConstants.COMMON_PRODUCT_TYPE,jsonString );
            System.out.println("==========from db==============");
        }else{
            //1.2 Redis中有就直接返回
                //返回Redis。将json字符串转换为list集合
            productTypes=JSON.parseArray(productTypeJson, ProductType.class);
            System.out.println("==========from cache==============");
        }
        //定义一个map集合。参数是id和productType类
        Map<Long,ProductType> map=new HashMap<>();
        //再循环查出来的对象,放到map集合中去
        for (ProductType cur : productTypes) {
            //将对象的id和对象放map集合中去
            map.put(cur.getId(), cur);
        }
        //2.组装父子结构。遍历上面查询到的数据,拿到当前对象
        for (ProductType current : productTypes) {
            //找到一级菜单
            if(current.getPid()==0){
                //装到上面定义的result集合中去
                result.add(current);
            }else{
                //否则就不是一级菜单,你是儿子。那么是哪个对象的儿子?
                //先定义一个老子菜单
                ProductType parent=null;

                /*嵌套循环了,更加影响数据库性能,所以不用,用上面定义的map集合方式存放查询出来的数据
                /再循环查出来的数据
                for (ProductType cur : productTypes) {
                    //如果cur对象的id就是current对象的pid,那么cur对象就是老子。多找一下表中的id和pid的关系
                    if(cur.getId()==current.getPid()){
                        //cur对象就是老子
                        parent=cur;
                    }
                }
                */
                //和上面嵌套循环一个意思。如果map集合中的cur对象的id就是current对象的pid,那么cur对象就是老子。多找一下表中的id和pid的关系
                parent = map.get(current.getPid());
                //然后拿到老子的儿子
                List<ProductType> children = parent.getChildren();
                //然后将你自己加进去。你自己是你老子的儿子
                children.add(current);
            }
        }
        //返回装好的一级菜单
        return result;
    }
}    
  • 图示解释service实现类的重点代码部分
    在这里插入图片描述
3)、在公共基础代码中aigou_basic_util中增加一个商品类型的常量的工具类AiGouConstants

在第2步service层的实现类中,会作为Redis的key键
在这里插入图片描述

4)、商品服务模块aigou_product_service的启动类需要feign的注解扫描

在这里插入图片描述

5)、公共服务模块的YAML配置。

在最上面的操作已经配置好了

6)、融断配置

融断要配在消费者那一边,而aigou_common_service_6699作为Redis的服务者,商品子模块aigou_product_service作为Redis的消费者。所以融断配置在商品子模块aigou_product_service中。

  • 在aigou_product_service子模块中的YAML配置文件中配置
    在这里插入图片描述
7)、启动7001注册中心、6699公共服务模块、8002商品模块服务
8)、使用postman测试Redis缓存接口
  • Redis的get接口方法。Redis工具类中测试了一个set方法存值。
    在这里插入图片描述
  • Redis的set接口方法。
    在这里插入图片描述
  • Redis的请求参数使用postman说明:
    在这里插入图片描述
9)、使用postman测试商品分类的接口图示

在这里插入图片描述

10)、Feign的使用总结

在这里插入图片描述

11)、上述Redis中央缓存问题:如何保证Redis的同步操作

保证Redis增删改数据的时候Redis同步操作。
商品分类的实现类ProductTypeServiceImpl继承baomidou包的serviceImpl实现类,serviceImpl中有更新数据的update方法。那么就可以直接重写父类的update方法,完成自己的功能即可。

  • 修改ProductTypeServiceImpl商品分类的service实现类代码
/**
 * <p>
 * 商品目录 服务实现类
 * </p>
 *
 * @author lyqtest
 * @since 2019-05-10
 */
@Service
public class ProductTypeServiceImpl extends ServiceImpl<ProductTypeMapper, ProductType> implements IProductTypeService {

    //注入mapper对象
    @Autowired
    private ProductTypeMapper productTypeMapper;

    //注入Redis接口
    @Autowired
    private RedisClient redisClient;

    //实现树状结构
    @Override
    public List<ProductType> treeData() {
        //递归方法。查看商品类型数据库表得知pid=0为最上一级。返回前台的都是一级菜单
        //return treeDataRecursion(0L);

        //循环方法。
        return treeDataLoop();
    }

    //重写继承ServiceImpl中的更新方法。完成Redis的同步操作
    @Override
    public boolean update(ProductType entity, Wrapper<ProductType> wrapper) {
        //先更新数据库
        boolean update = super.update(entity, wrapper);
        //再查询数据库,然后再调用下面定义的flushRedis()方法同步数据到Redis中去
        flushRedis();
        return update;
    }

    /**
     * 步骤:循环查询
     * 1:先查询出所有的数据
     * 2:再组装父子结构
     * @return
     */
    public List<ProductType> treeDataLoop() {
        //返回的一级菜单
        List<ProductType> result = new ArrayList<>();
        //1.先查询出所有的数据。selectList方法的参数mapper为null即可
        List<ProductType> productTypes=null;
        //List<ProductType> productTypes = productTypeMapper.selectList(null);
        //数据从Redis中获取
        //1.先通过key去Redis中获取json字符串
        String productTypeJson = redisClient.getRedis(AiGouConstants.COMMON_PRODUCT_TYPE);
        //1.1 Redis中没有这个key,那么就要从数据库去获取,并放入Redis中返回
        if(StringUtils.isEmpty(productTypeJson)){
            //Redis中没有数据
            //调用下面定义的flushRedis()方法从数据库中获取
            productTypes = flushRedis();
            System.out.println("==========from db 从数据库来==============");
        }else{
            //1.2 Redis中有就直接返回
                //返回Redis。将json字符串转换为list集合
            productTypes=JSON.parseArray(productTypeJson, ProductType.class);
            System.out.println("==========from cache 从缓存来==============");
        }
        //定义一个map集合。参数是id和productType类
        Map<Long,ProductType> map=new HashMap<>();
        //再循环查出来的对象,放到map集合中去
        for (ProductType cur : productTypes) {
            //将对象的id和对象放map集合中去
            map.put(cur.getId(), cur);
        }
        //2.组装父子结构。遍历上面查询到的数据,拿到当前对象
        for (ProductType current : productTypes) {
            //找到一级菜单
            if(current.getPid()==0){
                //装到上面定义的result集合中去
                result.add(current);
            }else{
                //否则就不是一级菜单,你是儿子。那么是哪个对象的儿子?
                //先定义一个老子菜单
                ProductType parent=null;
                //如果map集合中的cur对象的id就是current对象的pid,那么cur对象就是老子。多找一下表中的id和pid的关系
                parent = map.get(current.getPid());
                //然后拿到老子的儿子
                List<ProductType> children = parent.getChildren();
                //然后将你自己加进去。你自己是你老子的儿子
                children.add(current);
            }
        }
        //返回装好的一级菜单
        return result;
    }

    //从数据库中查询数据
    private List<ProductType> flushRedis() {
        List<ProductType> productTypes;//就从数据库中查询
        productTypes = productTypeMapper.selectList(null);
        //然后将数据存入到Redis中。使用fastJson技术
        //将list对象集合转换成json字符串
        String jsonString = JSON.toJSONString(productTypes);
        //将json字符串存入到Redis中。key/value
        redisClient.setRedis(AiGouConstants.COMMON_PRODUCT_TYPE,jsonString );
        return productTypes;
    }
}
  • 完成上述功能的重要代码图示:

大体的重要部分示意图,具体的看上面代码
在这里插入图片描述

12)、MySQL的数据和Redis初始化同步时机的两种方式:
  • 常归使用:在第一次使用 的时候,把数据从MySQL写到Redis。第一次很慢
  • 业务多,高并发使用:把需要缓存的数据,在项目启动的时候从数据库查询放入Redis。启动较慢,访问块
  • 根据业务需要选择上述两种

三、商品类型主页面页面静态化。velocity技术

虽然在上面的操作中,做了商品分类的Redis中央缓存。

1、页面静态化的含义

把动态页面使用静态页面替换:就是一个写死数据的html页面。将模板和需要的数据结合,生成一个静态的HTML页面。

2、使用页面静态化的原因

虽然加载商品分类时使用到了Redis中央缓存,但是如果主页的访问人数很多,那么也会给Redis带来很大的压力,那么以不发请求的静态页面代替要发请求静态页面或者动态页面。没有从后台获取数据:对后台的压力就减小了,同时页面不用再动态的渲染,用户体验更好

3、页面静态化的好处

(1)降低数据库或缓存压力
(2)高响应速度,增强用户体验

4、如何选择什么页面需要静态化

页面布局和数据不会经常变的,比如说首页。或者页面变化较小,比如详情页等等

5、常见的模板技术

(1)freemark
(2)velocity

6、什么时候需要使用静态页面

(1)、前台项目第一次启动的时候
(2)、模板关联的数据,发生变化的时候,就需要重新生成静态页面(首页就是商品分类数据)

7、velocity模板技术运用

完成今天的第二个功能,页面的静态化。由于页面静态化功能以后会在很多地方都需要调用,所以和Redis一样,将这个功能封装成一个服务,在之前的公共服务中增加静态化功能

(1)在公共服务模块aigou_common_parent中的公共接口子模块aigou_common_interface中创建静态化功能的接口和托底数据
1)、静态化接口。PageClient

在这里插入图片描述

2)、静态化功能的托底数据。PageFall

在这里插入图片描述

(2)提供velocity模板技术的工具类。VelocityUtil

velocity的工具类放在公共服务模块中的aigou_common_interface接口子模块中。

public class VelocityUtils {
	private static Properties p = new Properties();
	static {
		p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, "");
		p.setProperty(Velocity.ENCODING_DEFAULT, "UTF-8");
		p.setProperty(Velocity.INPUT_ENCODING, "UTF-8");
		p.setProperty(Velocity.OUTPUT_ENCODING, "UTF-8");
	}
	
	/**
	 * 返回通过模板,将model中的数据替换后的内容
	 * @param model
	 * @param templateFilePathAndName
	 * @return
	 */
	public static String getContentByTemplate(Object model, String templateFilePathAndName){
		try {
			Velocity.init(p);
			Template template = Velocity.getTemplate(templateFilePathAndName);
			VelocityContext context = new VelocityContext();
			context.put("model", model);
			StringWriter writer = new StringWriter();
			template.merge(context, writer);
			String retContent = writer.toString();
			writer.close();
			return retContent;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}

	/**
	 * 根据模板,静态化model到指定的文件 模板文件中通过访问model来访问设置的内容
	 * 
	 * @param model
	 *            数据对象
	 * @param templateFilePathAndName
	 *            模板文件的物理路径
	 * @param targetFilePathAndName
	 *            目标输出文件的物理路径
	 */
	public static void staticByTemplate(Object model, String templateFilePathAndName, String targetFilePathAndName) {
		try {
			Velocity.init(p);
			Template template = Velocity.getTemplate(templateFilePathAndName);
			
			VelocityContext context = new VelocityContext();
			context.put("model", model);
			FileOutputStream fos = new FileOutputStream(targetFilePathAndName);
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));// 设置写入的文件编码,解决中文问题
			template.merge(context, writer);
			writer.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 静态化内容content到指定的文件
	 * 
	 * @param content
	 * @param targetFilePathAndName
	 */
	public static void staticBySimple(Object content, String targetFilePathAndName) {
		VelocityEngine ve = new VelocityEngine();
		ve.init(p);
		String template = "${content}";
		VelocityContext context = new VelocityContext();
		context.put("content", content);
		StringWriter writer = new StringWriter();
		ve.evaluate(context, writer, "", template);
		try {
			FileWriter fileWriter = new FileWriter(new File(targetFilePathAndName));
			fileWriter.write(writer.toString());
			fileWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
(3)将模板工具类中需要的三个参数设定为常量

在公共基础模块aigou_basic_parent的工具类子模块aigou_basic_util中

在这里插入图片描述

(4)在公共服务模块aigou_common_parent模块的商品服务子模块aigou_common_service_6699的controller类中
@RestController
@RequestMapping("/common")
public class PageController implements PageClient {

    @RequestMapping(value = "/page",method = RequestMethod.POST)
    @Override
    public void createPage(@RequestBody Map<String, Object> map) {
        //获取参数:
        //调用封装的工具类:
        Object model=map.get(AiGouConstants.PAGE_MODEL);
        String templateFilePathAndName=(String)map.get(AiGouConstants.PAGE_TEMPLATEFILEPATHANDNAME);
        String targetFilePathAndName=(String)map.get(AiGouConstants.PAGE_TARGETFILEPATHANDNAME);
        System.out.println("model:"+model);
        System.out.println("templateFilePathAndName:"+templateFilePathAndName);
        System.out.println("targetFilePathAndName:"+targetFilePathAndName);
        VelocityUtils.staticByTemplate(model,templateFilePathAndName,targetFilePathAndName);
    }
}
(5)公共服务模块中的aigou_common_interface接口子模块的pom.xml中引入velocity的依赖

上面的操作中已经引入了

在这里插入图片描述

(6)在公共服务接口模块aigou_common_interface中的resource资源文件中拷贝进去页面的模板

模板很多的代码
在这里插入图片描述

(7)再次对service层实现类ProductServiceImpl进行修改。商品分类模块使用静态化功能最重要的部分代码。

由于在上面的Redis中央缓存操作,在商品主页面访问数量非常大的时候,会更Redis造成压力,所以再结合使用静态化页面功能

/**
 * <p>
 * 商品目录 服务实现类
 * </p>
 *
 * @author lyqtest
 * @since 2019-05-10
 */
@Service
public class ProductTypeServiceImpl extends ServiceImpl<ProductTypeMapper, ProductType> implements IProductTypeService {

    //注入mapper对象
    @Autowired
    private ProductTypeMapper productTypeMapper;

    //注入Redis接口
    @Autowired
    private RedisClient redisClient;

    //注入静态化接口
    @Autowired
    private PageClient pageClient;

    //同样是重写父类ServiceImpl的方法。这里解决静态化功能
    @Override
    public boolean updateById(ProductType entity) {
        //根据数据库
        boolean update = super.updateById(entity);
        //同步redis:
        List<ProductType> productTypes = flushRedis();
        //根据模板,使用数据,生成一个静态页面:
        List<ProductType> result=new ArrayList<>();
        makeStructure(result,productTypes);
        //先生成productType.html
        Map<String,Object> map = new HashMap<>();
        //设置三个参数:
        //需要的数据:就是List<ProductType>
        map.put(AiGouConstants.PAGE_MODEL,result);
        map.put(AiGouConstants.PAGE_TEMPLATEFILEPATHANDNAME,"H:\\IDEA\\idea_workspace\\aigou_parent\\aigou_common_parent\\aigou_common_interface\\src\\main\\resources\\template\\product.type.vm");
        map.put(AiGouConstants.PAGE_TARGETFILEPATHANDNAME,"H:\\IDEA\\idea_workspace\\aigou_parent\\aigou_common_parent\\aigou_common_interface\\src\\main\\resources\\template\\product.type.vm.html");
        pageClient.createPage(map);

        //再生成home.html
        Map<String,Object> homeMap = new HashMap<>();
        //设置三个参数:
        Map<String,String> staticRootMap=new HashMap<>();
        //根路径:
        staticRootMap.put("staticRoot","H:\\IDEA\\idea_workspace\\aigou_parent\\aigou_common_parent\\aigou_common_interface\\src\\main\\resources\\");
        homeMap.put(AiGouConstants.PAGE_MODEL,staticRootMap);
        homeMap.put(AiGouConstants.PAGE_TEMPLATEFILEPATHANDNAME,"H:\\IDEA\\idea_workspace\\aigou_parent\\aigou_common_parent\\aigou_common_interface\\src\\main\\resources\\template\\home.vm");
        homeMap.put(AiGouConstants.PAGE_TARGETFILEPATHANDNAME,"H:\\IDEA\\idea_workspace\\aigou_parent\\aigou_common_parent\\aigou_common_interface\\src\\main\\resources\\template\\home.html");
        pageClient.createPage(homeMap);
        
        return update;
    }

    //实现树状结构
    @Override
    public List<ProductType> treeData() {
        //递归方法。查看商品类型数据库表得知pid=0为最上一级。返回前台的都是一级菜单
        //return treeDataRecursion(0L);

        //循环方法。
        return treeDataLoop();
    }

    //重写继承ServiceImpl中的更新方法。完成Redis的同步操作
    @Override
    public boolean update(ProductType entity, Wrapper<ProductType> wrapper) {
        //先更新数据库
        boolean update = super.update(entity, wrapper);
        //再查询数据库,然后再调用下面定义的flushRedis()方法同步数据到Redis中去
        flushRedis();
        return update;
    }

    /**
     * 步骤:循环查询
     * 1:先查询出所有的数据
     * 2:再组装父子结构
     * @return
     */
    public List<ProductType> treeDataLoop() {
        //返回的一级菜单
        List<ProductType> result = new ArrayList<>();
        //1.先查询出所有的数据。selectList方法的参数mapper为null即可
        List<ProductType> productTypes=null;
        //List<ProductType> productTypes = productTypeMapper.selectList(null);
        //数据从Redis中获取
        //1.先通过key去Redis中获取json字符串
        String productTypeJson = redisClient.getRedis(AiGouConstants.COMMON_PRODUCT_TYPE);
        //1.1 Redis中没有这个key,那么就要从数据库去获取,并放入Redis中返回
        if(StringUtils.isEmpty(productTypeJson)){
            //Redis中没有数据
            //调用下面定义的flushRedis()方法从数据库中获取
            productTypes = flushRedis();
            System.out.println("==========from db 从数据库来==============");
        }else{
            //1.2 Redis中有就直接返回
                //返回Redis。将json字符串转换为list集合
            productTypes=JSON.parseArray(productTypeJson, ProductType.class);
            System.out.println("==========from cache 从缓存来==============");
        }
        //调用下面的makeStructure()方法,装入对应的菜单中
        makeStructure(result, productTypes);

        //返回装好的一级菜单
        return result;
    }

    private void makeStructure(List<ProductType> result, List<ProductType> productTypes) {
        //定义一个map集合。参数是id和productType类
        Map<Long,ProductType> map=new HashMap<>();
        //再循环查出来的对象,放到map集合中去
        for (ProductType cur : productTypes) {
            //将对象的id和对象放map集合中去
            map.put(cur.getId(), cur);
        }
        //2.组装父子结构。遍历上面查询到的数据,拿到当前对象
        for (ProductType current : productTypes) {
            //找到一级菜单
            if(current.getPid()==0){
                //装到上面定义的result集合中去
                result.add(current);
            }else{
                //否则就不是一级菜单,你是儿子。那么是哪个对象的儿子?
                //先定义一个老子菜单
                ProductType parent=null;

                /*嵌套循环了,更加影响数据库性能,所以不用,用上面定义的map集合方式存放查询出来的数据
                /再循环查出来的数据
                for (ProductType cur : productTypes) {
                    //如果cur对象的id就是current对象的pid,那么cur对象就是老子。多找一下表中的id和pid的关系
                    if(cur.getId()==current.getPid()){
                        //cur对象就是老子
                        parent=cur;
                    }
                }
                */
                //和上面嵌套循环一个意思。如果map集合中的cur对象的id就是current对象的pid,那么cur对象就是老子。多找一下表中的id和pid的关系
                parent = map.get(current.getPid());
                //然后拿到老子的儿子
                List<ProductType> children = parent.getChildren();
                //然后将你自己加进去。你自己是你老子的儿子
                children.add(current);
            }
        }
    }

    //从数据库中查询数据
    private List<ProductType> flushRedis() {
        List<ProductType> productTypes;//就从数据库中查询
        productTypes = productTypeMapper.selectList(null);
        //然后将数据存入到Redis中。使用fastJson技术
        //将list对象集合转换成json字符串
        String jsonString = JSON.toJSONString(productTypes);
        //将json字符串存入到Redis中。key/value
        redisClient.setRedis(AiGouConstants.COMMON_PRODUCT_TYPE,jsonString );
        return productTypes;
    }
}
  • 重要的代码图示

在这里插入图片描述

(8)使用postman接口测试,测试能否生成静态化HTML页面

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
该项目是采用目前比较流行的SpringBoot/SpringCloud构建微服务电商项目,项目叫 《果然新鲜》,实现一套串联的微服务电商项目。完全符合一线城市微服务电商的需求,对学习微服务电商架构,有非常大的帮助,该项目涵盖从微服务电商需求讨论、数据库设计、技术选型、互联网安全架构、整合SpringCloud各自组件、分布式基础设施等实现一套完整的微服务解决方案。 项目使用分布式微服务框架,涉及后台管理员服务、地址服务、物流服务、广告服务、商品服务、商品类别服务、品牌服务、订单服务 、购物车服务、首频道服务、公告服务、留言服务、搜索服务、会员服务等。  系统架构图   SpringBoot+SpringCloud+SSM构建微服务电商项目使用SpringCloud Eureka作为注册中心,实现服务治理使用Zuul网关框架管理服务请求入口使用Ribbon实现本地负载均衡器和Feign HTTP客户端调用工具使用Hystrix服务保护框架(服务降级、隔离、熔断、限流)使用消息总线Stream RabbitMQ和 Kafka微服务API接口安全控制和单点登录系统CAS+JWT+OAuth2.0分布式基础设施构建分布式任务调度平台XXL-JOB分布式日志采集系统ELK分布式事务解决方案LCN分布式锁解决方案Zookeeper、Redis分布式配置中心(携程Apollo)高并发分布式全局ID生成(雪花算法)分布式Session框架Spring-Session分布式服务追踪与调用链Zipkin项目运营与部署环境分布式设施环境,统一采用Docker安装使用jenkins+docker+k8s实现自动部署微服务API管理ApiSwagger使用GitLab代码管理(GitHub  GitEE)统一采用第三方云数据库使用七牛云服务器对静态资源实现加速 开发环境要求JDK统一要求:JDK1.8Maven统一管理依赖 统一采用Docker环境部署编码统一采用UTF-8开发工具IDEA 或者 Eclipse 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值