ID生成、异常与跨域-通用模块-Saas多租户平台开发

ID生成、异常与跨域-通用模块-Saas多租户平台开发


目录




内容

1、全局唯一ID生成

ID生成有很多种方案,这么我们选择基于’雪花算法’的ID生成,下面为生成ID的类:

	package com.ihrm.common.utils;

	import java.lang.management.ManagementFactory;
	import java.net.InetAddress;
	import java.net.NetworkInterface;

	/**
	 * <p>名称:IdWorker.java</p>
	 * <p>描述:分布式自增长ID</p>
	 * <pre>
	 *     Twitter的 Snowflake JAVA实现方案
	 * </pre>
	 * 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
	 * 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
	 * 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
	 * 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
	 * 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
	 * 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
	 * 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
	 * <p>
	 * 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
	 *
	 * @author Polim
	 */
	public class IdWorker {
		// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
		private final static long twepoch = 1288834974657L;
		// 机器标识位数
		private final static long workerIdBits = 5L;
		// 数据中心标识位数
		private final static long datacenterIdBits = 5L;
		// 机器ID最大值
		private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
		// 数据中心ID最大值
		private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
		// 毫秒内自增位
		private final static long sequenceBits = 12L;
		// 机器ID偏左移12位
		private final static long workerIdShift = sequenceBits;
		// 数据中心ID左移17位
		private final static long datacenterIdShift = sequenceBits + workerIdBits;
		// 时间毫秒左移22位
		private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

		private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
		/* 上次生产id时间戳 */
		private static long lastTimestamp = -1L;
		// 0,并发控制
		private long sequence = 0L;

		private final long workerId;
		// 数据标识id部分
		private final long datacenterId;

		public IdWorker(){
			this.datacenterId = getDatacenterId(maxDatacenterId);
			this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
		}
		/**
		 * @param workerId
		 *            工作机器ID
		 * @param datacenterId
		 *            序列号
		 */
		public IdWorker(long workerId, long datacenterId) {
			if (workerId > maxWorkerId || workerId < 0) {
				throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
			}
			if (datacenterId > maxDatacenterId || datacenterId < 0) {
				throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
			}
			this.workerId = workerId;
			this.datacenterId = datacenterId;
		}
		/**
		 * 获取下一个ID
		 *
		 * @return
		 */
		public synchronized long nextId() {
			long timestamp = timeGen();
			if (timestamp < lastTimestamp) {
				throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
			}

			if (lastTimestamp == timestamp) {
				// 当前毫秒内,则+1
				sequence = (sequence + 1) & sequenceMask;
				if (sequence == 0) {
					// 当前毫秒内计数满了,则等待下一秒
					timestamp = tilNextMillis(lastTimestamp);
				}
			} else {
				sequence = 0L;
			}
			lastTimestamp = timestamp;
			// ID偏移组合生成最终的ID,并返回ID
			long nextId = ((timestamp - twepoch) << timestampLeftShift)
					| (datacenterId << datacenterIdShift)
					| (workerId << workerIdShift) | sequence;

			return nextId;
		}

		private long tilNextMillis(final long lastTimestamp) {
			long timestamp = this.timeGen();
			while (timestamp <= lastTimestamp) {
				timestamp = this.timeGen();
			}
			return timestamp;
		}

		private long timeGen() {
			return System.currentTimeMillis();
		}

		/**
		 * <p>
		 * 获取 maxWorkerId
		 * </p>
		 */
		protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
			StringBuffer mpid = new StringBuffer();
			mpid.append(datacenterId);
			String name = ManagementFactory.getRuntimeMXBean().getName();
			if (!name.isEmpty()) {
			 /*
			  * GET jvmPid
			  */
				mpid.append(name.split("@")[0]);
			}
		  /*
		   * MAC + PID 的 hashcode 获取16个低位
		   */
			return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
		}

		/**
		 * <p>
		 * 数据标识id部分
		 * </p>
		 */
		protected static long getDatacenterId(long maxDatacenterId) {
			long id = 0L;
			try {
				InetAddress ip = InetAddress.getLocalHost();
				NetworkInterface network = NetworkInterface.getByInetAddress(ip);
				if (network == null) {
					id = 1L;
				} else {
					byte[] mac = network.getHardwareAddress();
					id = ((0x000000FF & (long) mac[mac.length - 1])
							| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
					id = id % (maxDatacenterId + 1);
				}
			} catch (Exception e) {
				System.out.println(" getDatacenterId: " + e.getMessage());
			}
			return id;
		}


	}

2、全局异常处理

  • 原理:全局异常处理基于@RestControllerAdvicet或者@ControllerAdvicet注解+@ExceptionHandler注解,详细可以自行查询相关文档
    步骤如下:
  1. 创建common.exception包统一放在异常处理相关类

  2. 创建异常类RRException继承RuntimeException,代码如下:

     package com.ihrm.common.exception;
    
     /**
      * 自定义异常
      */
     public class RRException extends RuntimeException {
     	private static final long serialVersionUID = 1L;
    
     	private String msg;
     	private int code = 500;
    
     	public RRException(String msg) {
     		super(msg);
     		this.msg = msg;
     	}
    
     	public RRException(String msg, Throwable e) {
     		super(msg, e);
     		this.msg = msg;
     	}
    
     	public RRException(String msg, int code) {
     		super(msg);
     		this.msg = msg;
     		this.code = code;
     	}
    
     	public RRException(String msg, int code, Throwable e) {
     		super(msg, e);
     		this.msg = msg;
     		this.code = code;
     	}
    
     	public String getMsg() {
     		return msg;
     	}
    
     	public void setMsg(String msg) {
     		this.msg = msg;
     	}
    
     	public int getCode() {
     		return code;
     	}
    
     	public void setCode(int code) {
     		this.code = code;
     	}
    
    
     }
    
  3. 创建全局异常处理类RRExceptionHandler.java,代码如下:

     package com.ihrm.common.exception;
    
    
     import com.ihrm.common.utils.R;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.springframework.dao.DuplicateKeyException;
     import org.springframework.web.bind.annotation.ExceptionHandler;
     import org.springframework.web.bind.annotation.RestControllerAdvice;
    
     /**
      * 异常处理器
      *
      * @author Mark [email protected]
      */
     @RestControllerAdvice
     public class RRExceptionHandler {
     	private Logger logger = LoggerFactory.getLogger(getClass());
    
     	/**
     	 * 处理自定义异常
     	 */
     	@ExceptionHandler(RRException.class)
     	public R handleRRException(RRException e){
     		R r = new R();
     		r.put("code", e.getCode());
     		r.put("msg", e.getMessage());
    
     		return r;
     	}
    
     	@ExceptionHandler(DuplicateKeyException.class)
     	public R handleDuplicateKeyException(DuplicateKeyException e){
     		logger.error(e.getMessage(), e);
     		return R.error("数据库中已存在该记录");
     	}
    
     //	@ExceptionHandler(AuthorizationException.class)
     //	public R handleAuthorizationException(AuthorizationException e){
     //		logger.error(e.getMessage(), e);
     //		return R.error("没有权限,请联系管理员授权");
     //	}
    
     	@ExceptionHandler(Exception.class)
     	public R handleException(Exception e){
     		logger.error(e.getMessage(), e);
     		return R.error();
     	}
     }
    

3、跨域配置

  • springboot中跨域配置,使用CorsFilter。为方面后续扩展其他过滤器,这么统一把过滤器放置在FilterConfig配置类中,代码如下:

      package com.ihrm.common.config;
    
      import org.springframework.boot.web.servlet.FilterRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.cors.CorsConfiguration;
      import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
      import org.springframework.web.filter.CorsFilter;
    
      import javax.servlet.Filter;
    
      /**
       * Filter配置
       */
      @Configuration
      public class FilterConfig {
    
      	@Bean
      	public FilterRegistrationBean corsFilter() {
      		FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
      		final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    
      		final CorsConfiguration corsConfiguration = new CorsConfiguration();
      		corsConfiguration.setAllowCredentials(true);
      		corsConfiguration.addAllowedOrigin("*");
      		corsConfiguration.addAllowedHeader("*");
      		corsConfiguration.addAllowedMethod("*");
      		corsConfiguration.setMaxAge(3600L);
      		source.registerCorsConfiguration("/**", corsConfiguration);
      		CorsFilter corsFilter = new CorsFilter(source);
      		registration.setFilter(corsFilter);
      		registration.setName("corsFilter");
      		return registration;
      	}
      }
    

4、说明

跨域处理一般会放置在网关模块或者业务模块中,但是这里还没有创建,等创建之后会移植到响应的模块中。

到此基础公共模块功能搭建完毕,下一步创建具体的功能模块。

后记

本项目为参考某马视频开发,相关视频及配套资料可自行度娘或者联系本人。上面为自己编写的开发文档,持续更新。欢迎交流,本人QQ:806797785

前端项目源代码地址:https://gitee.com/gaogzhen/ihrm-parent // 前端后台管理系统
    后端JAVA源代码地址:https://gitee.com/gaogzhen/ihrm-vue // 后端项目

猜你喜欢

转载自blog.csdn.net/gaogzhen/article/details/109696726