springcloud实战项目总结

springcloud

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包,Spring Cloud包含核心组件主要如下表所示(来自网络)。

微服务所需功能Spring Cloud
服务注册中心Spring Cloud Netflix Eureka
服务调用调用方式REST API
断路器Spring Cloud Netflix Hystrix
服务网关Spring Cloud Netflix Zuul
分布式配置Spring Cloud Config
服务跟踪Spring Cloud Sleuth
消息总线Spring Cloud Bus
数据流Spring Cloud Stream
批量任务Spring Cloud Task
链路追踪Spring Cloud Sleuth

Spring cloud各个组件的架构如下图:(图片来自网络)
在这里插入图片描述
通过这张图,我们来了解一下各组件配置使用运行流程:

  • 1.请求统一通过API网关(Zuul)来访问内部服务.
  • 2.网关接收到请求后,从注册中心(Eureka)获取可用服务
  • 3.由Ribbon进行均衡负载后,分发到后端具体实例
  • 4.微服务之间通过Feign进行通信处理业务
  • 5.Hystrix负责处理服务超时熔断
  • 6.Turbine监控服务间的调用和熔断相关指标
  • 7.Spring Cloud Sleuth为服务之间调用提供链路追踪

Eureka

Eureka是Netflix开源的一款提供服务注册和发现的产品,它提供了完整的Service Registry和Service Discovery实现。也是Spring Cloud体系中最重要最核心的组件之一,其实就是一个注册中心,将所有的可以提供的服务都注册到它这里来管理,其它各调用者需要的时候去注册中心获取,然后再进行调用,避免了服务之间的直接调用,方便后续的水平扩展、故障转移等。

Hystrix

在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
在这种情况下就需要整个服务机构具有故障隔离的功能,避免某一个服务挂掉影响全局。在Spring Cloud 中Hystrix组件就扮演这个角色。

Hystrix会在某个服务连续调用N次不响应的情况下,立即通知调用端调用失败,避免调用端持续等待而影响了整体服务。Hystrix间隔时间会再次检查此服务,如果服务恢复将继续提供服务。

Spring Cloud Config

随着微服务不断的增多,每个微服务都有自己对应的配置文件。在研发过程中有测试环境、UAT环境、生产环境,因此每个微服务又对应至少三个不同环境的配置文件。这么多的配置文件,如果需要修改某个公共服务的配置信息,如:缓存、数据库等,难免会产生混乱,这个时候就需要引入Spring Cloud另外一个组件:Spring Cloud Config。

Spring Cloud Config是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,Server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,Client通过接口获取数据、并依据此数据初始化自己的应用。其实就是Server端将所有的配置文件服务化,需要配置文件的服务实例去Config Server获取对应的数据。将所有的配置文件统一整理,避免了配置文件碎片化。

如果服务运行期间改变配置文件,服务是不会得到最新的配置信息,需要解决这个问题就需要引入Refresh。可以在服务的运行期间重新加载配置文件。

Spring Cloud Zuul

在微服务架构模式下,后端服务的实例数一般是动态的,对于客户端而言很难发现动态改变的服务实例的访问地址信息。因此在基于微服务的项目中为了简化前端的调用逻辑,通常会引入API Gateway作为轻量级网关,同时API Gateway中也会实现相关的认证逻辑从而简化内部服务之间相互调用的复杂度。

Spring Cloud体系中支持API Gateway落地的技术就是Zuul。Spring Cloud Zuul路由是微服务架构中不可或缺的一部分,提供动态路由,监控,弹性,安全等的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。

它的具体作用就是服务转发,接收并转发所有内外部的客户端调用。使用Zuul可以作为资源的统一访问入口,同时也可以在网关做一些权限校验等类似的功能。

项目应用

项目的部署

这次的项目中搭建了注册中心3个(集群),注册中心一个,zuul网关一个,服务多个(业务部分也可以认为是生产者)
在这里插入图片描述

在这里插入图片描述
具体的部署步骤在这里就不说了,这个不是今天的重点。

项目前要考虑的问题

向上抽取,使服务内部高内聚

package com.p2p.common_p2p.rpc.members;

import com.p2p.common_p2p.entity.members.Members;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Description:
 * @Author: cpc
 * @Date: 2019-12-10 10:21
 * @Version: V1.0
 */
@FeignClient(value = "MEMBERS")//被调用方服务名
public interface MembersFeignApi {
    /**
     * 通过ID查询单条数据
     *
     * @param id 主键
     * @return 实例对象
     */
    @GetMapping("/members/queryById")
    Members queryById(Integer id);

    /**
     * 这是修改用户账户信息
     * @param members
     * @return
     */
    @RequestMapping("/members/update")
    public int update(Members members);
}

redirs的应用和设计

在项目中我们的许多模块可能都需要使用到缓存,小编的这里的redirs就是在公共模块设计好然后就去使用就OK了。对于其他模块的使用redirs直接导入这些方法就行了。这里定义了一个redirs个工具类

package com.p2p.common_p2p.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * redis 工具类
 * @Author Scott
 *
 */
@Component
public class RedisUtil {

//	@Autowired
//	private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

	/**
	 * 指定缓存失效时间
	 *
	 * @param key  键
	 * @param time 时间(秒)
	 * @return
	 */
	public boolean expire(String key, long time) {
		try {
			if (time > 0) {
				redisTemplate.expire(key, time, TimeUnit.SECONDS);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("设置登录时长失败");
			return false;
		}
	}

	/**
	 * 根据key 获取过期时间
	 *
	 * @param key 键 不能为null
	 * @return 时间(秒) 返回0代表为永久有效
	 */
	public long getExpire(String key) {
		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
	}

	/**
	 * 判断key是否存在
	 *
	 * @param key 键
	 * @return true 存在 false不存在
	 */
	public boolean hasKey(String key) {
		try {
			return redisTemplate.hasKey(key);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 删除缓存
	 *
	 * @param key 可以传一个值 或多个
	 */
	@SuppressWarnings("unchecked")
	public void del(String... key) {
		if (key != null && key.length > 0) {
			if (key.length == 1) {
				redisTemplate.delete(key[0]);
			} else {
				redisTemplate.delete(CollectionUtils.arrayToList(key));
			}
		}
	}

	// ============================String=============================
	/**
	 * 普通缓存获取
	 *
	 * @param key 键
	 * @return 值
	 */
	public Object get(String key) {
		return key == null ? null : redisTemplate.opsForValue().get(key);
	}

	/**
	 * 普通缓存放入
	 *
	 * @param key   键
	 * @param value 值
	 * @return true成功 false失败
	 */
	public boolean set(String key, Object value) {
		try {
			redisTemplate.opsForValue().set(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

	}

	/**
	 * 普通缓存放入并设置时间
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
	 * @return true成功 false 失败
	 */
	public boolean set(String key, Object value, long time) {
		try {
			if (time > 0) {
				redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
			} else {
				set(key, value);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 递增
	 *
	 * @param key 键
	 * @param delta
	 * @return
	 */
	public long incr(String key, long delta) {
		if (delta < 0) {
			throw new RuntimeException("递增因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, delta);
	}

	/**
	 * 递减
	 *
	 * @param key 键
	 * @param delta  要减少几(小于0)
	 * @return
	 */
	public long decr(String key, long delta) {
		if (delta < 0) {
			throw new RuntimeException("递减因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, -delta);
	}

	// ================================Map=================================
	/**
	 * HashGet
	 *
	 * @param key  键 不能为null
	 * @param item 项 不能为null
	 * @return 值
	 */
	public Object hget(String key, String item) {
		return redisTemplate.opsForHash().get(key, item);
	}

	/**
	 * 获取hashKey对应的所有键值
	 *
	 * @param key 键
	 * @return 对应的多个键值
	 */
	public Map<Object, Object> hmget(String key) {
		return redisTemplate.opsForHash().entries(key);
	}

	/**
	 * HashSet
	 *
	 * @param key 键
	 * @param map map 就是项的 key-value对啦
	 * @return true 成功 false 失败
	 */
	public boolean hmset(String key, Map<String, Object> map) {
		try {
			redisTemplate.opsForHash().putAll(key, map);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * HashSet 并设置时间
	 *
	 * @param key  键
	 * @param map  对应多个键值
	 * @param time 时间(秒)
	 * @return true成功 false失败
	 */
	public boolean hmset(String key, Map<String, Object> map, long time) {
		try {
			redisTemplate.opsForHash().putAll(key, map);
			if (time > 0) {
				expire(key, time);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 向一张hash表中放入数据,如果不存在将创建
	 *
	 * @param key   键
	 * @param item  项
	 * @param value 值
	 * @return true 成功 false失败
	 */
	public boolean hset(String key, String item, Object value) {
		try {
			redisTemplate.opsForHash().put(key, item, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 向一张hash表中放入数据,并设置过期时间,如果不存在将创建
	 *
	 * @param key   键
	 * @param item  项
	 * @param value 值
	 * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
	 * @return true 成功 false失败
	 */
	public boolean hset(String key, String item, Object value, long time) {
		try {
			redisTemplate.opsForHash().put(key, item, value);
			if (time > 0) {
				expire(key, time);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 删除hash表中的值
	 *
	 * @param key  键 不能为null
	 * @param item 项 可以使多个 不能为null
	 */
	public void hdel(String key, Object... item) {
		redisTemplate.opsForHash().delete(key, item);
	}

	/**
	 * 判断hash表中是否有该项的值
	 *
	 * @param key  键 不能为null
	 * @param item 项 不能为null
	 * @return true 存在 false不存在
	 */
	public boolean hHasKey(String key, String item) {
		return redisTemplate.opsForHash().hasKey(key, item);
	}

	/**
	 * hash递增 如果不存在,就会创建一个 并把新增后的值返回
	 *
	 * @param key  键
	 * @param item 项
	 * @param by   要增加几(大于0)
	 * @return
	 */
	public double hincr(String key, String item, double by) {
		return redisTemplate.opsForHash().increment(key, item, by);
	}

	/**
	 * hash递减
	 *
	 * @param key  键
	 * @param item 项
	 * @param by   要减少记(小于0)
	 * @return
	 */
	public double hdecr(String key, String item, double by) {
		return redisTemplate.opsForHash().increment(key, item, -by);
	}

	// ============================set=============================
	/**
	 * 根据key获取Set中的所有值
	 *
	 * @param key 键
	 * @return
	 */
	public Set<Object> sGet(String key) {
		try {
			return redisTemplate.opsForSet().members(key);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 根据value从一个set中查询,是否存在
	 *
	 * @param key   键
	 * @param value 值
	 * @return true 存在 false不存在
	 */
	public boolean sHasKey(String key, Object value) {
		try {
			return redisTemplate.opsForSet().isMember(key, value);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将数据放入set缓存
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public long sSet(String key, Object... values) {
		try {
			return redisTemplate.opsForSet().add(key, values);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 将set数据放入缓存 并设置过期时间
	 *
	 * @param key    键
	 * @param time   时间(秒)
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public long sSetAndTime(String key, long time, Object... values) {
		try {
			Long count = redisTemplate.opsForSet().add(key, values);
			if (time > 0) {
				expire(key, time);
			}
			return count;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 获取set缓存的长度
	 *
	 * @param key 键
	 * @return
	 */
	public long sGetSetSize(String key) {
		try {
			return redisTemplate.opsForSet().size(key);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 移除值为value的
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return 移除的个数
	 */
	public long setRemove(String key, Object... values) {
		try {
			Long count = redisTemplate.opsForSet().remove(key, values);
			return count;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}
	// ===============================list=================================

	/**
	 * 获取list缓存的内容
	 *
	 * @param key   键
	 * @param start 开始
	 * @param end   结束 0 到 -1代表所有值
	 * @return
	 */
	public List<Object> lGet(String key, long start, long end) {
		try {
			return redisTemplate.opsForList().range(key, start, end);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 获取list缓存的长度
	 *
	 * @param key 键
	 * @return
	 */
	public long lGetListSize(String key) {
		try {
			return redisTemplate.opsForList().size(key);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 通过索引 获取list中的值
	 *
	 * @param key   键
	 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
	 * @return
	 */
	public Object lGetIndex(String key, long index) {
		try {
			return redisTemplate.opsForList().index(key, index);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @return
	 */
	public boolean lSet(String key, Object value) {
		try {
			redisTemplate.opsForList().rightPush(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒)
	 * @return
	 */
	public boolean lSet(String key, Object value, long time) {
		try {
			redisTemplate.opsForList().rightPush(key, value);
			if (time > 0) {
				expire(key, time);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @return
	 */
	public boolean lSet(String key, List<Object> value) {
		try {
			redisTemplate.opsForList().rightPushAll(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒)
	 * @return
	 */
	public boolean lSet(String key, List<Object> value, long time) {
		try {
			redisTemplate.opsForList().rightPushAll(key, value);
			if (time > 0) {
				expire(key, time);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 根据索引修改list中的某条数据
	 *
	 * @param key   键
	 * @param index 索引
	 * @param value 值
	 * @return
	 */
	public boolean lUpdateIndex(String key, long index, Object value) {
		try {
			redisTemplate.opsForList().set(key, index, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 移除N个值为value
	 *
	 * @param key   键
	 * @param count 移除多少个
	 * @param value 值
	 * @return 移除的个数
	 */
	public long lRemove(String key, long count, Object value) {
		try {
			Long remove = redisTemplate.opsForList().remove(key, count, value);
			return remove;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}
}

如何做分页

定义好一个分页分装类

package com.p2p.common_p2p.utils;

import java.util.LinkedHashMap;
import java.util.Map;
/**
 * 这是对查询分页条件的分装的
 *
 * LinkedHashMap ==> query == Map
 *
 *
 * query 前台的分页参数
 *       前台的查询参数  name=zhangs age =19
 */
public class Query extends LinkedHashMap<String, Object> {
	private static final long serialVersionUID = 1L;
	// 页数据
	private int page;
	// 每页条数
	private int rows;
	//总行数
	private long total;

	public Query(Map<String, Object> params) {
		this.putAll(params);
		if(params.get("page") != null){
			// 分页参数
			this.page = Integer.parseInt(params.get("page").toString());
		}else{
			this.page = 1;
		}
		if(params.get("rows") != null){
			// 分页参数
			this.rows = Integer.parseInt(params.get("rows").toString());
		}else{
			this.rows = 1;
		}
	}


	public Query(){
		super();
	}

	public int getPage() {
		return page;
	}

	public void setPage(int page) {
		this.page = page;
	}

	public int getRows() {
		return rows;
	}

	public void setRows(int rows) {
		this.rows = rows;
	}

	public long getTotal() {
		return total;
	}

	public void setTotal(long total) {
		this.total = total;
	}
}

分页的结果类

package com.p2p.common_p2p.utils;

import java.io.Serializable;
import java.util.List;

/**
 *
 * {
 *     total:199,
 *     data:[{name:zs,age:lx},{name:zs,age:lx},{name:zs,age:lx},{name:zs,age:lx},{name:zs,age:lx}]
 * }
 * 分页查询返回结果封装
 */
public class PageUtils implements Serializable {
	private static final long serialVersionUID = 1L;
	//这是总行数
	private long total;
	//这是保持查询出来的数据
	private List<?> data;

	/**
	 *
	 * @param list 保存数据
	 * @param total 查到多行数据
	 */
	public PageUtils(List<?> list, long total) {
		this.data = list;
		this.total = total;
	}

	public long getTotal() {
		return total;
	}

	public void setTotal(int total) {
		this.total = total;
	}

    public List<?> getData() {
        return data;
    }

    public void setData(List<?> data) {
        this.data = data;
    }

    public static long getSerialVersionUID() {
		return serialVersionUID;
	}
}

还有一个切面类,它才是关键

package com.p2p.common_p2p.aspect;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wyy.common_p2p.rpc.sys.SysdictitemFeignApi;
import com.wyy.common_p2p.utils.Query;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @Description: 这是实现aop分页
 * @Author: cpc
 * @Date: 2019-10-15 15:12
 * @Version: V1.0
 */
@Component
@Aspect
public class PagerAspect {


    private SysdictitemFeignApi sysdictitemFeignApi;
    /**
     * 拦截Pager 结尾的方法 做分页
     * @param args
     * @return
     * @throws Throwable
     */
    @Around("execution( * com.p2p.*.service.impl.*.*Pager(..))")
    public Object invoke(ProceedingJoinPoint args) throws Throwable {
        //获取产生列表
        Object[] params = args.getArgs();
        Query query = null;
        //找PageBean对象
        for (Object param : params) {
            if(param instanceof Query){
                query = (Query)param;
                break;
            }
        }
        //如果pageBean对象不为空就开启分页
        if(query != null) {
            PageHelper.startPage(query.getPage(), query.getRows());
        }
        //执行方法并且获取了结果集
        Object list = args.proceed(params);
        //将分页结果信息放入到PageBean对象中
        if(null != query){
            PageInfo pageInfo = new PageInfo((List) list);
            query.setTotal(pageInfo.getTotal());
        }
        return list;
    }
}

对于它的应用

/**
     * 分页查询
     *
     * @param  params 请求参数集
     * @return 结果集封装对象
     */
    @GetMapping("queryPager")
    public PageUtils queryPager(@RequestParam Map<String, Object> params) {
        Query query = new Query(params);
        List<Permission> list = permissionService.queryPager(query);
        return new PageUtils(list, query.getTotal());
    }

数据字典的应用

首先先自定义一个注解方便我们的使用

package com.p2p.common_p2p.aspect.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *  类描述:  字典注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {

    /**
     * 方法描述:  数据code
     * @return 返回类型: String
     */
    String dicCode();

    /**
     * 方法描述:  这是返回后Put到josn中的文本 key 值
     * @return 返回类型: String
     */
    String dicText() default "";
}

字典的界面类

package com.p2p.common_p2p.aspect;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.p2p.common_p2p.aspect.annotation.Dict;
import com.p2p.common_p2p.constant.CommonConstant;
import com.p2p.common_p2p.rpc.sys.SysdictitemFeignApi;
import com.p2p.common_p2p.utils.PageUtils;
import com.p2p.common_p2p.utils.oConvertUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Description: 字典aop类 (用于翻译字典数据)
 * @Author: cpc
 * @Date: 2019-3-17 21:50
 * @Version: 1.0
 */
@Aspect
@Component
@Slf4j
public class DictAspect {
    //服务名
    @Autowired
    private SysdictitemFeignApi sysdictitemFeignApi;

    // 定义切点Pointcut
    @Pointcut("execution( * com.wyy..controller.*.*(..))")
    public void excudeService() {
    }

    /**
     * 这是触发 excudeService 的时候会执行的
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        //这是定义开始事件
    	long time1= System.currentTimeMillis();
    	//这是方法并获取返回结果
        Object result = pjp.proceed();
        //这是获取到 结束时间
        long time2= System.currentTimeMillis();
        log.debug("获取JSON数据 耗时:"+(time2-time1)+"ms");
        //解析开始时间
        long start= System.currentTimeMillis();
        //开始解析(翻译字段内部的值)
        this.parseDictText(result);
        //解析结束时间
        long end= System.currentTimeMillis();
        log.debug("解析注入JSON数据  耗时"+(end-start)+"ms");
        return result;
    }

    /**
     * 本方法针对返回对象为Result 的IPage的分页列表数据进行动态字典注入
     * 字典注入实现 通过对实体类添加注解@dict 来标识需要的字典内容,字典分为单字典code即可 ,table字典 code table text配合使用与原来jeecg的用法相同
     * 示例为SysUser   字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
     * 例输入当前返回值的就会多出一个sex_dictText字段
     * {
     *      sex:1,
     *      sex_dictText:"男"
     * }
     * 前端直接取值sext_dictText在table里面无需再进行前端的字典转换了
     *  customRender:function (text) {
     *               if(text==1){
     *                 return "男";
     *               }else if(text==2){
     *                 return "女";
     *               }else{
     *                 return text;
     *               }
     *             }
     *             目前vue是这么进行字典渲染到table上的多了就很麻烦了 这个直接在服务端渲染完成前端可以直接用
     * @param result
     */
    private void parseDictText(Object result) {
        if (result instanceof PageUtils) {
                List<JSONObject> items = new ArrayList<>();
                PageUtils pageUtils = (PageUtils)result;
                //循环查找出来的数据
                for (Object record :  pageUtils.getData()) {
                    ObjectMapper mapper = new ObjectMapper();
                    String json="{}";
                    try {
                        //解决@JsonFormat注解解析不了的问题详见SysAnnouncement类的@JsonFormat
                        json = mapper.writeValueAsString(record);
                    } catch (JsonProcessingException e) {
                        log.error("json解析失败"+e.getMessage(),e);
                    }
                    JSONObject item = JSONObject.parseObject(json);

                    //update-begin--Author:scott -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
                    //for (Field field : record.getClass().getDeclaredFields()) {
                    for (Field field : oConvertUtils.getAllFields(record)) {
                        //update-end--Author:scott  -- Date:20190603 ----for:解决继承实体字段无法翻译问题------
                        if (field.getAnnotation(Dict.class) != null) {
                            String code = field.getAnnotation(Dict.class).dicCode();
                            String text = field.getAnnotation(Dict.class).dicText();
                            //获取当前带翻译的值
                            String key = String.valueOf(item.get(field.getName()));
                            //翻译字典值对应的txt
                            String textValue = translateDictValue(code, key);

                            log.debug(" 字典Val : "+ textValue);
                            //  CommonConstant.DICT_TEXT_SUFFIX的值为,是模块
                            // public static final String DICT_TEXT_SUFFIX = "_dictText";
                            log.debug(" __翻译字典字段__ "+field.getName() + CommonConstant.DICT_TEXT_SUFFIX+": "+ textValue);
                            //如果给了文本名
                            if(!StringUtils.isEmpty(text)){
                                item.put(text, textValue);
                            }else{
                                //走默认策略
                                item.put(field.getName() + CommonConstant.DICT_TEXT_SUFFIX, textValue);
                            }

                        }
                        //date类型默认转换string格式化日期
                        if (field.getType().getName().equals("java.util.Date")&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
                            SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
                        }
                    }
                    items.add(item);
                }
                 pageUtils.setData(items);

            }
    }


    /**
          *  翻译字典文本
     * @param code
     * @param key
     * @return
     */
    private String translateDictValue(String code, String key) {
        //如果key为空直接返回就好了
    	if(oConvertUtils.isEmpty(key)) {
    		return null;
    	}
        StringBuffer textValue=new StringBuffer();
    	//分割 key 值
        String[] keys = key.split(",");
        //循环 keys 中的所有值
        for (String k : keys) {
            String tmpValue = null;
            log.debug(" 字典 key : "+ k);
            if (k.trim().length() == 0) {
                continue; //跳过循环
            }
            tmpValue = sysdictitemFeignApi.queryDictTextByKey(code, k.trim());

            if (tmpValue != null) {
                if (!"".equals(textValue.toString())) {
                    textValue.append(",");
                }
                textValue.append(tmpValue);
            }
        }
        //返回翻译的值
        return textValue.toString();
    }

}

项目里常用的常量

package com.p2p.common_p2p.constant;

/**
 * @Description: 定义公共常量
 * @Author: cpc
 * @Date: 2019-10-19 11:33
 * @Version: V1.0
 */
public class CommonConstant {
    /* JWT_WEB_TTL:WEBAPP应用中token的有效时间,默认30分钟 */
    public static final long  JWT_WEB_TTL  = 30 * 60 * 1000;;

    /*  将jwt令牌保存到header中的key名称 用于权限认证*/
//    public static final String JWT_HEADER_KEY = "Authorization";
    public static final String JWT_HEADER_KEY = "jwt";
    /*这是保存验证码颁发的jwt令牌*/
    public static final String JWT_VERIFICATION_KEY = "verificationJwt";

    /* 用户存入reids中的验证码前缀*/
    public static final String VERIFICATION_PREFIX = "verificationCode_";

    /*后台管理人员登录成功后保存在redis中的jwt字符串前缀*/
    public static final String PREFIX_USER_TOKEN  = "PREFIX_USER_TOKEN_";

    /*客户登录成功后保存在redis中的jwt字符串前缀*/
    public static final String PREFIX_MEBERS_TOKEN = "PREFIX_MEBERS_TOKEN_";

    /*这是前台的角色名*/
    public static final String FONT_DESK_ROLE_NAME = "font_desk";

    /**字典翻译文本后缀*/
    public static final String DICT_TEXT_SUFFIX = "_dictText";

}

常用的工具类,包括判空,ip,获取父类。。。。

package com.p2p.common_p2p.utils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Description: 常用工具类
 * @Author: cpc
 * @Date: 2019-10-21 14:19
 * @Version: V1.0
 */
public class oConvertUtils {


    /**
     * 获取类的所有属性,包括父类
     *
     * @param object
     * @return
     */
    public static Field[] getAllFields(Object object) {
        Class<?> clazz = object.getClass();
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }


        public static boolean isEmpty(Object object) {
            if (object == null) {
                return (true);
            }
            if ("".equals(object)) {
                return (true);
            }
            if ("null".equals(object)) {
                return (true);
            }
            return (false);
        }


    /**
     * @param request 更具request 对象获取到ip
     *            IP
     * @return IP Address
     */
    public static String getIpAddrByRequest(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    public static boolean isNotEmpty(Object object) {
        if (object != null && !object.equals("") && !object.equals("null")) {
            return (true);
        }
        return (false);
    }
}

哪一个注解在哪使用?在实体类里面
这个sy就是字典的标识,然后就是字典的单表的增删改查

  //用户标识: 0 不可用 1 可用
    @Dict(dicCode = "sy")
    private Integer userFlag;

通用返回类型

小编的这个项目是使用的vue作为前台,vue的格式是

{
code:0,
msg:数据
}

在这里之前我们是需要在每一个返回的方法里面都添加员工map集合,然后以集合的方法返回到前台这样就会有很多的重复代码为了减少代码的重复性增加代码的使用率这里编辑了一个通用的返回类型:

package com.p2p.common_p2p.utils;

import java.util.HashMap;
import java.util.Map;


/**
 * @Description: 这是返回结果集封装对象 (应为继承了HashMap所以非常扩展成自己想要用到的样子)
 * @Author: cpc
 * @Date: 2019-10-15 22:07
 * @Version: V1.0
 * r == Map
 *
 *
 * new R()
 * {
 *     code:500,
 *     msg: 操作失败
 * }
 *
 * query  ===> 封装查询参数   (分页 查询参数  )
 *
 * PageUtile ====》 封装返回结果 (totlo data)
 *
 */
public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public R() {
        put("code", 0);
        put("msg", "操作成功");
    }


    public static R error() {
        return error(500, "操作失败");
    }

    public static R error(String msg) {
        return error(500, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }


    /**
     * 这是更具状态来觉得返回信息,如果 大于 1 成功 如果小于 1 失败
     * @param n
     * @return
     */
    public static R update(int n) {
        return n > 0? R.ok("操作成功") : R.error("操作失败");
    }


    @Override
    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}

解决zuul跨域问题

在这里插入图片描述
ZuulConfig类

package com.p2p.zuul_3001_p2p.config;

import com.p2p.zuul_3001_p2p.filter.MyCorsFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description: zuul 配置类
 * @Author: cpc
 * @Date: 2019-12-11 11:49
 * @Version: V1.0
 */
@Configuration
public class ZuulConfig {

    /**
     * 这是配置 跨域组件
     * @return
     */
    @Bean
    public MyCorsFilter accessFilter(){
        return new MyCorsFilter();
    }
}

MyCorsFilter类

package com.p2p.zuul_3001_p2p.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class MyCorsFilter extends ZuulFilter {

    /**
     * 判断该过滤器是否要被执行
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器的具体执行逻辑
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest httpRequest = ctx.getRequest();
        HttpServletResponse httpResponse = ctx.getResponse();

        // Access-Control-Allow-Origin就是我们需要设置的域名
        // Access-Control-Allow-Headers跨域允许包含的头。
        // Access-Control-Allow-Methods是允许的请求方式
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
        httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");

        //允许客户端发送一个新的请求头
        httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, jwt, verificationJwt");
        //允许客户端处理一个新的响应头 切记如果是多个使用 "," 隔开千万不要再setHeader以为能添加
        httpResponse.setHeader("Access-Control-Expose-Headers", "jwt,verificationJwt");
        //axios和ajax会发送两次请求,第一次提交方式为:option直接返回即可
        if("OPTIONS".equals(httpRequest.getMethod())) {
            return false;
        }
        return null;
    }

    /**
     * 过滤器的类型 这里用pre,代表会再请求被路由之前执行
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤器的执行顺序
     */
    @Override
    public int filterOrder() {
        return 0;
    }
}

启动类:

package com.p2p.zuul_3001_p2p;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class Zuul3001P2pApplication {
    public static void main(String[] args) {
        SpringApplication.run(Zuul3001P2pApplication.class, args);
    }
    /**
     *这是跨域配置
     * @return
     */
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 允许cookies跨域
        config.addAllowedOrigin("*");// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080 ,以降低安全风险。。
        config.addAllowedHeader("*");// 允许访问的头信息,*表示全部
        config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
        config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");// 允许Get的请求方法
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);

    }
}



版权声明:本文为what_where原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。