日期时间的处理
import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.calendar;import java.util.Date;public final class DateUtils {public static boolean isLegalDate(String str, String pattern){try {SimpleDateFormat format = new SimpleDateFormat(pattern);format.parse(str);return true;} catch (Exception e){return false;}}public static Date parseString2Date(String str,String pattern){try {SimpleDateFormat format = new SimpleDateFormat(pattern);return format.parse( str);}catch (ParseException e){e.printstackTrace();return null;}}public static calendar parseString2calendar(String str,String pattern){return parseDate2Calendar(parsestring2Date(str, pattern));}public static String parseLong2DateString(long date,String pattern){SimpleDateFormat sdf = new SimpleDateFormat(pattern);String sd = sdf.format(new Date(date));return sd;}public static Calendar parseDate2Calendar(Date date){Calendar calendar = Calendar.getInstance();calendar.setTime(date);return calendar;}public static Date parseCalendar2Date(calendar calendar){return calendar.getTime();}public static String parseCalendar2String(calendar calendar,String pattern){return parseDate2String(parsecalendar2Date(calendar), pattern);}public static String parseDate2String(Date date,String pattern) {SimpleDateFormat format = new SimpleDateFormat(pattern);return format.format(date);}public static String formatTime( long time){long nowTime = System.currentTimeMillis();long interval = nowTime - time;long hours = 3600 * 1000;long days = hours * 24;long fiveDays = days *5;if (interval
在处理日期格式时,我们可以调用上述代码提供的方法,如判断日期是否合法的方法isLegalDate。我们在做日期转换时,可以调用以 parse开头的这些方法,通过方法名大致能知道其含义,如parseCalendar2String表示将calendar类型的对象转化为String类型,parseDate2String 表示将Date类型的对象转化为string类型,parseString2Date表示将String类型转化为Date类型。
joda-time joda-time artifactId>2.10.1
Joda-Time是一个高效的日期处理工具,它作为JDK原生日期时间类的替代方案,被越来越多的人使用。在进行日期时间处理时,你可优先考虑它。
public final class StringUtils{private static final char[] CHARS ={ '0','1','2','3', '4', '5','6', '7',' 8','9'};private static int char_length =CHARS.length;public static boolean isEmpty( string str){return null == str ll str.length()== 0;}public static boolean isNotEmpty(string str){return !isEmpty(str);}public static boolean isBlank(String str){int strLen;if (null == str ll(strLen = str.length())== 0){return true;}for (int i= e; i
commons-codec commons-codec commons-io commons-io artifactId>2.6
在上述依赖中,commons-codec是 Apache基金会提供的用于信息摘要和 Base64编码解码的包。在常见的对称和非对称加密算法中,都会对密文进行 Base64编码。而 commons-io是 Apache基金会提供的用于操作输入输出流的包。在对RSA 的加密/解密算法中,需要用到字节流的操作,因此需要添加此依赖包。
import javax.crypto.spec. SecretKeySpec;public class AesEncryptUtils {private static final String ALGORITHMSTR = "AES/ECB/PKCSSPadding";public static String base64Encode(byte[] bytes) ireturn Base64.encodeBase64String( bytes);}public static byte[] base64Decode(String base64Code) throws Exception {return Base64.decodeBase64(base64Code);}public static byte[] aesEncryptToBytes(String content,String encryptKey) throwsException {KeyGenerator kgen = KeyGenerator.getInstance("AES");kgen.init(128);Cipher cipher = Cipher.getInstance(ALGORITHMSTR);cipher.init(Cipher.ENCRYPT_MODE,new SecretKeySpec(encryptKey.getBytes(),"AES"));return cipher.doFinal(content.getBytes("utf-8"));}public static String aesEncrypt(String content, String encryptKey) throwS Exception {return base64Encode(aesEncryptToBytes(content,encryptKey));}public static string aesDecryptByBytes(byte[] encryptBytes, String decryptKey)throwsException {KeyGenerator kgen = KeyGenerator.getInstance("AES");kgen.init(128);Cipher cipher = Cipher.getInstance(ALGORITHMSTR);cipher.init(Cipher.DECRYPT_MODE,new SecretKeySpec(decryptKey.getBytes(),"AES"));byte[] decryptBytes = cipher.doFinal(encryptBytes);return new String(decryptBytes);}public static String aesDecrypt(String encryptStr, String decryptKey) throwsException ireturn aesDecryptByBytes(base64Decode(encryptStr),decryptKey);}}
上述代码是通用的AES加密算法,加密和解密需要统一密钥,密钥是自定义的任意字符串,长度为16位、24位或32位。这里调用aesEncrypt方法进行加密,其中第一个参数为明文,第二个参数为密钥;调用aesDecrypt进行解密,其中第一个参数为密文,第二个参数为密钥。
public class RSAUtils {public static final String CHARSET ="UTF-8";public static final String RSA_ALGORITHM="RSA";public static MapcreateKeys(int keySize){KeyPairGenerator kpg;try{kpg =KeyPairGenerator.getInstance(RSA_ALGORITHM);Security.addProvider(new com.sun.crypto.provider. SunJCE());}catch(NoSuchAlgorithmException e){throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM +"]");}kpg.initialize(keySize);KeyPair keyPair = kpg.generateKeyPair();Key publicKey = keyPair.getPublic();string publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());Key privateKey = keyPair.getPrivate();String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());Map keyPairMap = new HashMap(2);keyPairMap.put("publicKey", publicKeyStr);keyPairMap.put( "privateKey", privateKeyStr);return keyPairMap;}public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException,InvalidKeySpecException {KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);x509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey)) ;RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic( x509KeySpec);return key;}public static RSAPrivateKey getPrivateKey(String privateKey) throwsNoSuchAlgorithmException,InvalidKeySpecException {KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);return key;}public static String publicEncrypt(String data,RSAPublicKey publicKey){try{Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");cipher.init(Cipher. ENCRYPT_MODE,publicKey);return Base64.encodeBase64String(rsaSplitCodec(cipher,Cipher. ENCRYPT_MODE,data.getBytes(CHARSET),publicKey.getModulus().bitLength()));}catch(Exception e){throw new RuntimeException("加密字符串["+data +"]时遇到异常",e);}}public static String privateDecrypt(String data,RSAPrivateKey privateKey){try{Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");cipher.init(Cipher. DECRYPT_MODE, privateKey);return new String(rsaSplitCodec(cipher,Cipher. DECRYPT_MODE,Base64.decodeBase64(data),privateKey.getModulus().bitLength()),CHARSET);}catch(Exception e){e.printStackTrace();throw new RuntimeException("解密字符串["+data+"]时遇到异常",e);}}private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas,int keySize){int maxBlock = 0;if(opmode == Cipher. DECRYPT_MODE){maxBlock = keysize / 8;}else{maxBlock =keysize / 8 -11;}ByteArrayOutputStream out = new ByteArrayoutputStream();int offSet = 0;byte[] buff;int i = 0;try{while(datas. length > offSet)fif(datas.length-offSet > maxBlock){buff = cipher.doFinal(datas,offSet,maxBlock);}else{buff = cipher.doFinal(datas,offSet, datas.length-offSet);}out.write(buff, 0,buff.length);i++;offSet = i * maxBlock;}}catch(Exception e){e.printStackTrace();throw new RuntimeException("加解密阈值为["+maxBlock+"]的数据时发生异常",e);}byte[] resultDatas = out.toByteArray();IOUtils.closeQuietly(out);return resultDatas;}}
前面提到了RSA是一种非对称加密算法,所谓非对称,即加密和解密所采用的密钥是不一样的。RSA 的基本思想是通过一定的规则生成一对密钥,分别是公钥和私钥,公钥是提供给客户端使用的,即任何人都可以得到,而私钥存放到服务端,任何人都不能通过正常渠道拿到。
import java.security.MessageDigest;public class MessageDigestutils {public static string encrypt(String password,string algorithm){try {MessageDigest md =MessageDigest.getInstance(algorithm);byte[] b = md.digest(password.getBytes("UTF-8"));return ByteUtils.byte2HexString(b);}catch (Exception e){e.printStackTrace();return null;}}}
JDK自带信息摘要算法,但返回的是字节数组类型,在实际中需要将字节数组转化成十六进制字符串,因此上述代码对信息摘要算法做了简要的封装。通过调用MessageDigestutils.encrypt方法即可返回加密后的字符串密文,其中第一个参数为明文,第二个参数为具体的信息摘要算法,可选值有MD5、SHA1和SHA256等。
消息队列的封装
可以看到,RabbitMQ已启动。在默认情况下,RabbitMQ安装后只开启5672端口,我们只能通过命令的方式查看和管理RabbitMQ。为了方便,我们可以通过安装插件来开启RabbitMQ的 Web管理功能。打开cmd命令控制台,进入 RabbitMQ安装目录的 sbin目录,输入 rabbitmq-plugins enablerabbitmq_management即可,如图6-2所示。
Web管理界面的默认启动端口为15672。在浏览器中输人localhost:15672,默认的账号和密码都是guest,填写后可以进入Web管理主界面,如图6-3所示。
接下来,我们就封装消息队列。(1)添加 RabbitMQ依赖:
org.springframework.cloud spring-cloud-starter-bus-amqp artifactId>
import org.springframework.amqp.core.Queue;import org.springframework.boot.SpringBootConfiguration;import org.springframework.context.annotation. Bean;@SpringBootConfigurationpublic class Rabbitconfiguration {@Beanpublic Queue queue(){return new Queue( "someQueue");}}
前面已经讲过,Spring Boot可以利用@SpringBootConfiguration注解对应用程序进行配置。我们集成RabbitMQ依赖后,也需要对其进行基本配置。在上述代码中,我们定义了一个 Bean,该Bean的作用是自动创建消息队列名。如果不通过代码创建队列,那么每次都需要手动去RabbitMQ的Web管理界面添加队列,否则会报错,如图6-4所示。
但是每次都通过Web管理界面手动创建队列显然不可取,因此,我们可以在上述配置类中事先定义好队列。
@Componentpublic class MyBean {private final AmqpAdmin amqpAdmin;private final AmqpTemplate amqpTemplate;@Autowiredpublic MyBean(AmqpAdmin amqpAdmin,AmqpTemplate amqpTemplate){this.amqpAdmin = amqpAdmin;this.amqpTemplate = amqpTemplate;}@RabbitHandler@RabbitListener(queues = "someQueue")public void processMessage(String content){//消息队列消费者system.out.println( content);}public void send(string content){//消息队列生产者amqpTemplate.convertAndSend("someQueue", content);}}
其中,send为消息生产者,负责发送队列名为someQueue 的消息,processNessage为消息消费者,在其方法上定义了@RabbitHandler和@RabbitListener注解,表示该方法为消息消费者,并且指定了消费哪种队列。
@RequestMapping("/v1/index")
@Target(ElementType. TYPE)@Retention(RetentionPolicy.RUNTIME)@Mapping@Documentedpublic @interface ApiVersion {int value();}
在上面的代码中,首先定义了一个注解,用于指定控制器的版本号,比如@ApiVersion(1),则通过地址v1/**就可以访问该控制器定义的方法。
public class CustomRequestMappingHandlerMapping extendsRequestMappingHandlerMapping i@overrideprotected RequestCondition getCustomTypeCondition(Class>handlerType) {ApiVersion apiVersion = Annotationutils.findAnnotation(handlerType,Apiversion.class);return createCondition( apiversion);}@overrideprotected RequestCondition createCondition(ApiVersion apiVersion)freturn apiversion == null ? null : new ApiVersionCondition(apiVersion.value());}}
Spring MVC通过RequestMapping 来定义请求路径,因此如果我们要自动通过v1这样的地址来请求指定的控制器,就应该继承RequestMappingHandlerMapping类来重写其方法。
@Overridepublic RequestMappingHandlerMapping requestMappingHandlerMapping(){RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();handlerMapping.set0rder(0);return handlerMapping;}
在上述代码中,我们重写了requestMappingHandlerMapping方法并实例化了RequestMapping-HandlerMapping对象,返回的是前面自定义的CustomRequestMappingHandlerMapping类。
@RequestMapping("{version}")@RestController@ApiVersion(1)public class TestV1controller{@GetMapping("index ")public String index(){return "";}}
protected void validate(BindingResult result){if(result.hasFieldErrors()){List errorList = result.getFieldErrors();errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));}}
然后在控制器接口的参数中添加@valid注解,后面紧跟 BindingResult类,在方法体中调用validate(result)方法即可,如:
@GetMapping( "index")public String index(@valid TestRequest request, BindingResult result){validate(result);return "Hello " +request.getName();}
@Datapublic class TestRequest {@NotEmptyprivate String name;}
可以看出,直接在界面上返回了500,这不是我们期望的。正常情况下,即便出错,也应返回统一的JSON格式,如:
{"code" :0,"message" :"不能为空" ,"data" :null}
@ExceptionHandlerpublic SingleResult doError(Exception exception){if(Stringutils.isBlank(exception.getMessage())){return SingleResult.buildFailure();}return SingleResult.buildFailure(exception.getMessage());}
com.alibaba fastjson 1.2.47 dependency>
@overridepublic void configureMessageConverters(List> converters){super.configureMessageConverters(converters);FastJsonHttpMessageConverter fastConverter=new Fast]sonHttpMessageConverter();FastJsonConfig fastJsonconfig=new FastsonConfig();fastJsonconfig.setSerializerFeatures(SerializerFeature.PrettyFormat);List mediaTypeList = new ArrayList();mediaTypeList.add(MediaType.APPLICATION_JSON_UTF8);fastConverter.setSupportedMediaTypes(mediaTypeList);fastConverter.setFastsonConfig(fastsonConfig);converters.add(fastConverter);}
/*** Override this method to add custom {@link HttpMessageConverter}s to use* with the {@link RequestMappingHandlerAdapter} and the* {@link ExceptionHandlerExceptionResolver}. Adding converters to the* list turns off the default converters that would otherwise be registered* by default. Also see {@link #addDefaultHttpNessageConverters(List)} that* can be used to add default message converters.* @param converters a list to add message converters to;* initially an empty list.*/protected void configureMessageConverters(List> converters) {}
org.springframework.boot spring-boot-starter-data-redis
@Componentpublic class Redis i@Autowiredprivate StringRedisTemplate template;public void set(String key, String value,long expire){template.opsForValue().set(key, value,expire,TimeUnit.SECONDS);}public void set(String key,string value){template.opsForValue().set(key, value);}public Object get(String key) ireturn template.opsForValue().get(key);}public void delete(String key) {template.delete(key);}}
在上述代码中,我们先注入StringRedisTemplate类,该类是Spring Boot 提供的Redis操作模板类,通过它的名称可以知道该类专门用于字符串的存取操作,它继承自RedisTemplate类。代码中只实现了Redis的基本操作,包括键值保存、读取和删除操作。set方法重载了两个方法,可以接收数据保存的有效期,TimeUnit.SECONDS 指定了该有效期单位为秒。读者如果在项目开发过程中发现这些操作不能满足要求时,可以在这个类中添加方法满足需求。
参与评论
手机查看
返回顶部