Commit c11b787d by 刘鑫

CI: 添加自定义json转换工具类, 减少三方依赖度

1 parent 39a95b6c
package com.dituhui.pea.order.common.jackson;
import lombok.experimental.UtilityClass;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.util.concurrent.TimeUnit;
/**
* DateTime 工具类
*
* @author liuxin
*/
@UtilityClass
public class DateTimeUtil {
/**
* yyyy-MM-dd HH:mm:ss
*/
public static final DateTimeFormatter DATETIME_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATETIME);
/**
* 日期格式 yyyy-MM-dd
*/
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATE);
/**
* 时间格式 HH:mm:ss
*/
public static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
/**
* 日期时间格式化
*
* @param temporal 时间
* @return 格式化后的时间
*/
public static String formatDateTime(TemporalAccessor temporal) {
return DATETIME_FORMAT.format(temporal);
}
/**
* 日期时间格式化
*
* @param temporal 时间
* @return 格式化后的时间
*/
public static String formatDate(TemporalAccessor temporal) {
return DATE_FORMAT.format(temporal);
}
/**
* 时间格式化
*
* @param temporal 时间
* @return 格式化后的时间
*/
public static String formatTime(TemporalAccessor temporal) {
return TIME_FORMAT.format(temporal);
}
/**
* 日期格式化
*
* @param temporal 时间
* @param pattern 表达式
* @return 格式化后的时间
*/
public static String format(TemporalAccessor temporal, String pattern) {
return DateTimeFormatter.ofPattern(pattern).format(temporal);
}
/**
* 将字符串转换为时间
*
* @param dateStr 时间字符串
* @param pattern 表达式
* @return 时间
*/
public static TemporalAccessor parse(String dateStr, String pattern) {
DateTimeFormatter format = DateTimeFormatter.ofPattern(pattern);
return format.parse(dateStr);
}
/**
* 将字符串转换为时间
*
* @param dateStr 时间字符串
* @param formatter DateTimeFormatter
* @return 时间
*/
public static TemporalAccessor parse(String dateStr, DateTimeFormatter formatter) {
return formatter.parse(dateStr);
}
/**
* 时间转 Instant
*
* @param dateTime 时间
* @return Instant
*/
public static Instant toInstant(LocalDateTime dateTime) {
return dateTime.atZone(ZoneId.of("+8")).toInstant();
}
/**
* Instant 转 时间
*
* @param instant Instant
* @return Instant
*/
public static LocalDateTime toDateTime(Instant instant) {
return LocalDateTime.ofInstant(instant, ZoneId.of("+8"));
}
/**
* 获取当前东八区时间
*
* @return LocalDateTime东八区对象
*/
public static LocalDateTime generateLocalDateTime() {
return LocalDateTime.now(ZoneId.of("+8"));
}
/**
* 求两个时间差
*
* @param start 开始时间
* @param end 结束时间
* @param timeUnit 时间差单位 {@link TimeUnit}
* @return 指定单位时间差
*/
public static long betweenTwoTime(Temporal start, Temporal end, TimeUnit timeUnit) {
Duration duration = Duration.between(start, end);
long result;
switch (timeUnit) {
case DAYS:
result = duration.toDays();
break;
case HOURS:
result = duration.toHours();
break;
case MINUTES:
result = duration.toMinutes();
break;
case SECONDS:
result = duration.getSeconds();
break;
case MILLISECONDS:
result = duration.toMillis();
break;
case NANOSECONDS:
result = duration.toNanos();
break;
default:
throw new IllegalStateException("Unexpected value: " + timeUnit);
}
return result;
}
}
package com.dituhui.pea.order.common.jackson;
import lombok.experimental.UtilityClass;
import org.springframework.util.Assert;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalQuery;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* 日期工具类
*
* @author liuxin
*/
@UtilityClass
public class DateUtil {
/**
* yyyy-MM-dd HH:mm:ss
*/
public static final String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss";
/**
* 日期 yyyy-MM-dd格式
*/
public static final String PATTERN_DATE = "yyyy-MM-dd";
/**
* 时间格式 HH:mm:ss
*/
public static final String PATTERN_TIME = "HH:mm:ss";
/**
* java 8 时间格式化
*/
public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATETIME);
/**
* 日期 yyyy-MM-dd格式
*/
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_DATE);
/**
* 时间格式 HH:mm:ss
*/
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DateUtil.PATTERN_TIME);
/**
* 获取当前日期
*
* @return 当前日期
*/
public static Date now() {
return new Date();
}
/**
* 添加年
*
* @param date 时间
* @param yearsToAdd 添加的年数
* @return 设置后的时间
*/
public static Date plusYears(Date date, int yearsToAdd) {
return DateUtil.set(date, Calendar.YEAR, yearsToAdd);
}
/**
* 添加月
*
* @param date 时间
* @param monthsToAdd 添加的月数
* @return 设置后的时间
*/
public static Date plusMonths(Date date, int monthsToAdd) {
return DateUtil.set(date, Calendar.MONTH, monthsToAdd);
}
/**
* 添加周
*
* @param date 时间
* @param weeksToAdd 添加的周数
* @return 设置后的时间
*/
public static Date plusWeeks(Date date, int weeksToAdd) {
return DateUtil.plus(date, Period.ofWeeks(weeksToAdd));
}
/**
* 添加天
*
* @param date 时间
* @param daysToAdd 添加的天数
* @return 设置后的时间
*/
public static Date plusDays(Date date, long daysToAdd) {
return DateUtil.plus(date, Duration.ofDays(daysToAdd));
}
/**
* 添加小时
*
* @param date 时间
* @param hoursToAdd 添加的小时数
* @return 设置后的时间
*/
public static Date plusHours(Date date, long hoursToAdd) {
return DateUtil.plus(date, Duration.ofHours(hoursToAdd));
}
/**
* 添加分钟
*
* @param date 时间
* @param minutesToAdd 添加的分钟数
* @return 设置后的时间
*/
public static Date plusMinutes(Date date, long minutesToAdd) {
return DateUtil.plus(date, Duration.ofMinutes(minutesToAdd));
}
/**
* 添加秒
*
* @param date 时间
* @param secondsToAdd 添加的秒数
* @return 设置后的时间
*/
public static Date plusSeconds(Date date, long secondsToAdd) {
return DateUtil.plus(date, Duration.ofSeconds(secondsToAdd));
}
/**
* 添加毫秒
*
* @param date 时间
* @param millisToAdd 添加的毫秒数
* @return 设置后的时间
*/
public static Date plusMillis(Date date, long millisToAdd) {
return DateUtil.plus(date, Duration.ofMillis(millisToAdd));
}
/**
* 添加纳秒
*
* @param date 时间
* @param nanosToAdd 添加的纳秒数
* @return 设置后的时间
*/
public static Date plusNanos(Date date, long nanosToAdd) {
return DateUtil.plus(date, Duration.ofNanos(nanosToAdd));
}
/**
* 日期添加时间量
*
* @param date 时间
* @param amount 时间量
* @return 设置后的时间
*/
public static Date plus(Date date, TemporalAmount amount) {
Instant instant = date.toInstant();
return Date.from(instant.plus(amount));
}
/**
* 减少年
*
* @param date 时间
* @param years 减少的年数
* @return 设置后的时间
*/
public static Date minusYears(Date date, int years) {
return DateUtil.set(date, Calendar.YEAR, -years);
}
/**
* 减少月
*
* @param date 时间
* @param months 减少的月数
* @return 设置后的时间
*/
public static Date minusMonths(Date date, int months) {
return DateUtil.set(date, Calendar.MONTH, -months);
}
/**
* 减少周
*
* @param date 时间
* @param weeks 减少的周数
* @return 设置后的时间
*/
public static Date minusWeeks(Date date, int weeks) {
return DateUtil.minus(date, Period.ofWeeks(weeks));
}
/**
* 减少天
*
* @param date 时间
* @param days 减少的天数
* @return 设置后的时间
*/
public static Date minusDays(Date date, long days) {
return DateUtil.minus(date, Duration.ofDays(days));
}
/**
* 减少小时
*
* @param date 时间
* @param hours 减少的小时数
* @return 设置后的时间
*/
public static Date minusHours(Date date, long hours) {
return DateUtil.minus(date, Duration.ofHours(hours));
}
/**
* 减少分钟
*
* @param date 时间
* @param minutes 减少的分钟数
* @return 设置后的时间
*/
public static Date minusMinutes(Date date, long minutes) {
return DateUtil.minus(date, Duration.ofMinutes(minutes));
}
/**
* 减少秒
*
* @param date 时间
* @param seconds 减少的秒数
* @return 设置后的时间
*/
public static Date minusSeconds(Date date, long seconds) {
return DateUtil.minus(date, Duration.ofSeconds(seconds));
}
/**
* 减少毫秒
*
* @param date 时间
* @param millis 减少的毫秒数
* @return 设置后的时间
*/
public static Date minusMillis(Date date, long millis) {
return DateUtil.minus(date, Duration.ofMillis(millis));
}
/**
* 减少纳秒
*
* @param date 时间
* @param nanos 减少的纳秒数
* @return 设置后的时间
*/
public static Date minusNanos(Date date, long nanos) {
return DateUtil.minus(date, Duration.ofNanos(nanos));
}
/**
* 日期减少时间量
*
* @param date 时间
* @param amount 时间量
* @return 设置后的时间
*/
public static Date minus(Date date, TemporalAmount amount) {
Instant instant = date.toInstant();
return Date.from(instant.minus(amount));
}
/**
* 设置日期属性
*
* @param date 时间
* @param calendarField 更改的属性
* @param amount 更改数,-1表示减少
* @return 设置后的时间
*/
private static Date set(Date date, int calendarField, int amount) {
Assert.notNull(date, "The date must not be null");
Calendar c = Calendar.getInstance();
c.setLenient(false);
c.setTime(date);
c.add(calendarField, amount);
return c.getTime();
}
/**
* 将字符串转换为时间
*
* @param dateStr 时间字符串
* @param pattern 表达式
* @param query TemporalQuery
* @param <T> 转换目标类型
* @return 时间
*/
public static <T> T parse(String dateStr, String pattern, TemporalQuery<T> query) {
return DateTimeFormatter.ofPattern(pattern).parse(dateStr, query);
}
/**
* 时间转 Instant
*
* @param dateTime 时间
* @return Instant
*/
public static Instant toInstant(LocalDateTime dateTime) {
return dateTime.atZone(ZoneId.of("+8")).toInstant();
}
/**
* Instant 转 时间
*
* @param instant Instant
* @return Instant
*/
public static LocalDateTime toDateTime(Instant instant) {
return LocalDateTime.ofInstant(instant, ZoneId.of("+8"));
}
/**
* 转换成 date
*
* @param dateTime LocalDateTime
* @return Date
*/
public static Date toDate(LocalDateTime dateTime) {
return Date.from(DateUtil.toInstant(dateTime));
}
/**
* 转换成 date
*
* @param localDate LocalDate
* @return Date
*/
public static Date toDate(final LocalDate localDate) {
return Date.from(localDate.atStartOfDay(ZoneId.of("+8")).toInstant());
}
/**
* Converts local date time to Calendar.
*
* @param localDateTime 需要转换掉时间
* @return Calendar对象
*/
public static Calendar toCalendar(final LocalDateTime localDateTime) {
return GregorianCalendar.from(ZonedDateTime.of(localDateTime, ZoneId.of("+8")));
}
/**
* localDateTime 转换成毫秒数
*
* @param localDateTime LocalDateTime
* @return long
*/
public static long toMilliseconds(final LocalDateTime localDateTime) {
return localDateTime.atZone(ZoneId.of("+8")).toInstant().toEpochMilli();
}
/**
* localDate 转换成毫秒数
*
* @param localDate LocalDate
* @return long
*/
public static long toMilliseconds(LocalDate localDate) {
return toMilliseconds(localDate.atStartOfDay());
}
/**
* 转换成java8 时间
*
* @param calendar 日历
* @return LocalDateTime
*/
public static LocalDateTime fromCalendar(final Calendar calendar) {
TimeZone tz = calendar.getTimeZone();
ZoneId zid = tz == null ? ZoneId.of("+8") : tz.toZoneId();
return LocalDateTime.ofInstant(calendar.toInstant(), zid);
}
/**
* 转换成java8 时间
*
* @param instant Instant
* @return LocalDateTime
*/
public static LocalDateTime fromInstant(final Instant instant) {
return LocalDateTime.ofInstant(instant, ZoneId.of("+8"));
}
/**
* 转换成java8 时间
*
* @param date Date
* @return LocalDateTime
*/
public static LocalDateTime fromDate(final Date date) {
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("+8"));
}
/**
* 转换成java8 时间
*
* @param milliseconds 毫秒数
* @return LocalDateTime
*/
public static LocalDateTime fromMilliseconds(final long milliseconds) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(milliseconds), ZoneId.of("+8"));
}
/**
* 比较2个时间差,跨度比较小
*
* @param startInclusive 开始时间
* @param endExclusive 结束时间
* @return 时间间隔
*/
public static Duration between(Temporal startInclusive, Temporal endExclusive) {
return Duration.between(startInclusive, endExclusive);
}
/**
* 比较2个时间差,跨度比较大,年月日为单位
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 时间间隔
*/
public static Period between(LocalDate startDate, LocalDate endDate) {
return Period.between(startDate, endDate);
}
/**
* 比较2个 时间差
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 时间间隔
*/
public static Duration between(Date startDate, Date endDate) {
return Duration.between(startDate.toInstant(), endDate.toInstant());
}
/**
* 将秒数转换为日时分秒
*
* @param second 秒数
* @return 时间
*/
//CHECKSTYLE:OFF
@SuppressWarnings("checkstyle:MagicNumber")
public static String secondToTime(Long second) {
// 判断是否为空
if (second == null || second == 0L) {
return "";
}
//转换天数
long days = second / 86400;
//剩余秒数
second = second % 86400;
//转换小时
long hours = second / 3600;
//剩余秒数
second = second % 3600;
//转换分钟
long minutes = second / 60;
//剩余秒数
second = second % 60;
if (days > 0) {
return MessageFormat.format("{}天{}小时{}分{}秒", days, hours, minutes, second);
} else {
return MessageFormat.format("{}小时{}分{}秒", hours, minutes, second);
}
}
//CHECKSTYLE:ON
}
package com.dituhui.pea.order.common.jackson;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone;
/**
* json 工具类 基于Jackson
*
* @author liuxin
*/
@UtilityClass
@Slf4j
public class JsonUtil {
/**
* 序列化对象为 json string
*
* @param value java对象
* @param <T> 泛型标记
* @return json字符串, 转换失败返回空白
*/
public static <T> String toJson(T value) {
try {
return value instanceof String ? (String) value : getInstance().writeValueAsString(value);
} catch (JsonProcessingException e) {
log.error("[JsonUtil][toJson] ------> {}", e.getMessage(), e);
return "";
}
}
/**
* 实体转格式化jsonString
*
* @param entity java对象
* @param <E> 泛型标记
* @return json string
*/
public static <E> String objectToJsonPretty(E entity) {
try {
return entity instanceof String ? (String) entity
: getInstance().writerWithDefaultPrettyPrinter()
.writeValueAsString(entity);
} catch (Exception e) {
log.error("[JsonUtil][toJson]----> Parse Object to Json error,ex:{}", e.getMessage(), e);
return "";
}
}
/**
* 实体转格式化jsonString 去除空值
*
* @param entity java对象
* @param <E> 泛型标记
* @return json string
*/
public static <E> String objectToJsonPrettyNoneNull(E entity) {
try {
return entity instanceof String ? (String) entity
: getNonNullInstance().writerWithDefaultPrettyPrinter()
.writeValueAsString(entity);
} catch (Exception e) {
log.error("[JsonUtil][toJson]----> Parse Object to Json error,ex:{}", e.getMessage(), e);
return "";
}
}
/**
* 将对象序列化成 json byte 数组
*
* @param object javaBean
* @return jsonString json字符串
*/
public static byte[] toJsonAsBytes(Object object) {
try {
return getInstance().writeValueAsBytes(object);
} catch (JsonProcessingException e) {
log.error("[JsonUtil][toJsonAsBytes]----> Parse Object to Json as byte error,ex:{}", e.getMessage(), e);
}
return new byte[0];
}
/**
* 将json反序列化成对象
*
* @param content content
* @param clazz class
* @param <T> T 泛型标记
* @return Bean
*/
public static <T> Optional<T> parse(String content, Class<T> clazz) {
try {
return Optional.of(getInstance().readValue(content, clazz));
} catch (Exception e) {
log.error("[JsonUtil][parse]----> Parse string to object with type error,ex:{}", e.getMessage(), e);
}
return Optional.empty();
}
/**
* 将json反序列化成对象
*
* @param content content
* @param typeReference 泛型类型
* @param <T> T 泛型标记
* @return Bean
*/
public static <T> Optional<T> parse(String content, TypeReference<T> typeReference) {
try {
return Optional.of(getInstance().readValue(content, typeReference));
} catch (IOException e) {
log.error("[JsonUtil][parse]----> Parse string to object with type reference error,ex:{}", e.getMessage(),
e);
}
return Optional.empty();
}
/**
* 将json字符串转成 JsonNode
*
* @param jsonString jsonString
* @return jsonString json字符串
*/
public static Optional<JsonNode> readTree(String jsonString) {
try {
return Optional.of(getInstance().readTree(jsonString));
} catch (IOException e) {
log.error("[JsonUtil][readTree]----> Parse string to json node error,ex:{}", e.getMessage(), e);
}
return Optional.empty();
}
/**
* 将json字符串转换为集合
*
* @param src jsonString
* @param collectionClass 集合类型 List, Set, Map
* @param elementClasses 集合内元素类型
* @param <T> 泛型标记
* @return 集合
*/
public static <T> Optional<T> parse(String src, Class<?> collectionClass, Class<?>... elementClasses) {
if (StringUtils.isBlank(src)) {
return Optional.empty();
}
JavaType javaType = getInstance().getTypeFactory().constructParametricType(collectionClass, elementClasses);
try {
return Optional.ofNullable(getInstance().readValue(src, javaType));
} catch (Exception e) {
log.error("【JsonUtil】---->Parse Json to collection error,ex:{}", e.getMessage());
return Optional.empty();
}
}
/**
* ObjectMapper 实例获取 (序列化后包含null值)
* @return ObjectMapper实例
*/
public static ObjectMapper getInstance() {
return JacksonHolder.INSTANCE;
}
/**
* ObjectMapper 实例获取 (序列化后不包含null值)
* @return ObjectMapper实例
*/
public static ObjectMapper getNonNullInstance() {
final ObjectMapper instance = new JacksonObjectMapper();
instance.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return instance;
}
/**
* 获取全局 JacksonObjectMapper
*/
private static class JacksonHolder {
private static final ObjectMapper INSTANCE = new JacksonObjectMapper();
}
/**
* 定义 JacksonObjectMapper 序列化
*/
public static class JacksonObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 4288193147502386170L;
private static final Locale CHINA = Locale.CHINA;
/**
* 构造器初始化
*/
public JacksonObjectMapper() {
super();
// 设置地点为中国
super.setLocale(CHINA);
// 去掉默认的时间戳格式
super.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
// 设置为中国上海时区
super.setTimeZone(TimeZone.getTimeZone("+8"));
// 序列化时,日期的统一格式
super.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
// 序列化处理
super.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
super.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
super.findAndRegisterModules();
// 失败处理
super.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
super.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 单引号处理
super.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 反序列化时,属性不存在的兼容处理s
super.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 日期格式化
super.registerModule(new LJavaTimeModule());
super.findAndRegisterModules();
}
}
}
package com.dituhui.pea.order.common.jackson;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* 定义java.util.time 序列化格式
*/
public class LJavaTimeModule extends SimpleModule {
/**
* 构造器定义时间序列化格式
*/
public LJavaTimeModule() {
super(PackageVersion.VERSION);
this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeUtil.DATETIME_FORMAT));
this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeUtil.DATE_FORMAT));
this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeUtil.TIME_FORMAT));
this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeUtil.DATETIME_FORMAT));
this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeUtil.DATE_FORMAT));
this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeUtil.TIME_FORMAT));
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!