Commit 5594c218 authored by liqin's avatar liqin 💬

bug fixed

parent fbc86c49
......@@ -267,6 +267,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
......
package cn.wisenergy.chnmuseum.party.common.util;
import jdk.nashorn.internal.parser.DateParser;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class TimeUtils {
// 格式:年-月-日 小时:分钟:秒
public static final String FORMAT_ONE = "yyyy-MM-dd HH:mm:ss";
public static final String FORMAT_T = "yyyy-MM-ddTHH:mm:ss";
// 格式:年-月-日 小时:分钟
public static final String FORMAT_TWO = "yyyy-MM-dd HH:mm";
// 格式:年-月-日
public static final String LONG_DATE_FORMAT = "yyyy-MM-dd";
// 格式:月-日
public static final String SHORT_DATE_FORMAT = "MM-dd";
// 格式:小时:分钟:秒
public static final String LONG_TIME_FORMAT = "HH:mm:ss";
// 格式:年-月
public static final String MONTG_DATE_FORMAT = "yyyy-MM";
// 格式:年-月
public static final String Chinese_DATE_FORMAT = "yyyy年MM月dd日 HH:mm";
private static final ConcurrentMap<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>();
private static final int PATTERN_CACHE_SIZE = 500;
public static LocalDate getCurrentDate = LocalDate.now();
public static LocalTime getCurrentTime = LocalTime.now().withNano(0);
/**
* Date转换为long
*
* @param date date
* @return
*/
public static long parseDate(Date date) {
return date.getTime() / 1000L;
}
/**
* Date转换为格式化时间
*
* @param date date
* @param pattern 格式
* @return
*/
public static String format(Date date, String pattern) {
return format(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()), pattern);
}
/**
* localDateTime转换为格式化时间
*
* @param localDateTime localDateTime
* @param pattern 格式
* @return
*/
public static String format(LocalDateTime localDateTime, String pattern) {
DateTimeFormatter formatter = createCacheFormatter(pattern);
return localDateTime.format(formatter);
}
/**
* 格式化字符串转为Date
*
* @param time 格式化时间
* @param pattern 格式
* @return
*/
public static Date parseDate(String time, String pattern) {
return Date.from(parseLocalDateTime(time, pattern).atZone(ZoneId.systemDefault()).toInstant());
}
/**
* 格式化字符串转为LocalDateTime
*
* @param time 格式化时间
* @param pattern 格式
* @return
*/
public static LocalDateTime parseLocalDateTime(String time, String pattern) {
DateTimeFormatter formatter = createCacheFormatter(pattern);
return LocalDateTime.parse(time, formatter);
}
/**
* 将某时间字符串转为自定义时间格式的LocalDateTime
*
* @param time
* @param format
* @return
*/
public static LocalDateTime parseStringToDateTime(String time, String format) {
DateTimeFormatter df = DateTimeFormatter.ofPattern(format);
return LocalDateTime.parse(time, df);
}
/**
* java.util.Date --> java.time.LocalDateTime
*/
public static LocalDateTime UDateToLocalDateTime(Date date) {
Instant instant = date.toInstant();
ZoneId zone = ZoneId.systemDefault();
return LocalDateTime.ofInstant(instant, zone);
}
/**
* 将LocalDateTime转为自定义的时间格式的字符串
*
* @param localDateTime
* @param format
* @return
*/
public static String getDateTimeAsString(LocalDateTime localDateTime, String format) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
return localDateTime.format(formatter);
}
/**
* 将long类型的timestamp转为LocalDateTime
*
* @param timestamp
* @return
*/
public static LocalDateTime getDateTimeOfTimestamp(long timestamp) {
Instant instant = Instant.ofEpochMilli(timestamp);
ZoneId zone = ZoneId.systemDefault();
return LocalDateTime.ofInstant(instant, zone);
}
/**
* 将LocalDateTime转为long类型的timestamp
*
* @param localDateTime
* @return
*/
public static long getTimestampOfDateTime(LocalDateTime localDateTime) {
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDateTime.atZone(zone).toInstant();
return instant.toEpochMilli();
}
public static long getTimestampOfDateTime8(LocalDateTime localDateTime) {
ZoneId zone = ZoneId.of("Asia/Shanghai");
Instant instant = localDateTime.atZone(zone).toInstant();
return instant.toEpochMilli();
}
/**
* 在缓存中创建DateTimeFormatter
*
* @param pattern 格式
* @return
*/
private static DateTimeFormatter createCacheFormatter(String pattern) {
if (pattern == null || pattern.length() == 0) {
throw new IllegalArgumentException("Invalid pattern specification");
}
DateTimeFormatter formatter = FORMATTER_CACHE.get(pattern);
if (formatter == null) {
if (FORMATTER_CACHE.size() < PATTERN_CACHE_SIZE) {
formatter = DateTimeFormatter.ofPattern(pattern);
DateTimeFormatter oldFormatter = FORMATTER_CACHE.putIfAbsent(pattern, formatter);
if (oldFormatter != null) {
formatter = oldFormatter;
}
}
}
return formatter;
}
/**
* 解析时间字符串
*
* @param dateString ECMA 5.1 DateString
*/
public static LocalDateTime parseUTCDateTime(String dateString) {
DateParser dateParser = new DateParser(dateString);
if (dateParser.parse()) {
Integer[] dateFields = dateParser.getDateFields();
LocalDateTime localDateTime = LocalDateTime.of(dateFields[0], dateFields[1] + 1, dateFields[2], dateFields[3], dateFields[4], dateFields[5], dateFields[6]);
if (dateFields[7] != null) {
return localDateTime.minusMinutes(dateFields[7]);
}
return localDateTime;
}
throw new DateTimeParseException("解析时间出现错误:" + dateString, dateString, 0);
}
public static ZonedDateTime parseZonedDateTime(String dateString) {
DateParser dateParser = new DateParser(dateString);
if (dateParser.parse()) {
Integer[] dateFields = dateParser.getDateFields();
if (dateFields[7] != null) {
return ZonedDateTime.of(dateFields[0], dateFields[1] + 1, dateFields[2], dateFields[3], dateFields[4], dateFields[5], dateFields[6], ZoneId.ofOffset("", ZoneOffset.ofHoursMinutes(dateFields[7] / 60, dateFields[7] % 60)));
}
return ZonedDateTime.of(dateFields[0], dateFields[1] + 1, dateFields[2], dateFields[3], dateFields[4], dateFields[5], dateFields[6], ZoneId.systemDefault());
}
throw new DateTimeParseException("解析时间出现错误:" + dateString, dateString, 0);
}
/**
* Date转换为LocalDateTime
*
* @param date
*/
public static LocalDateTime date2LocalDateTime(Date date) {
Instant instant = date.toInstant();//An instantaneous point on the time-line.(时间线上的一个瞬时点。)
ZoneId zoneId = ZoneId.systemDefault();//A time-zone ID, such as {@code Europe/Paris}.(时区)
return instant.atZone(zoneId).toLocalDateTime();
}
/**
* LocalDateTime转换为Date
*
* @param localDateTime
*/
public static Date localDateTime2Date(LocalDateTime localDateTime) {
ZoneId zoneId = ZoneId.systemDefault();
ZonedDateTime zdt = localDateTime.atZone(zoneId);//Combines this date-time with a time-zone to create a ZonedDateTime.
return Date.from(zdt.toInstant());
}
/**
* 取本月第一天
*/
public static LocalDate firstDayOfThisMonth() {
LocalDate today = LocalDate.now();
return today.with(TemporalAdjusters.firstDayOfMonth());
}
/**
* 取本月第N天
*/
public static LocalDate dayOfThisMonth(int n) {
LocalDate today = LocalDate.now();
return today.withDayOfMonth(n);
}
/**
* 取本月最后一天
*/
public static LocalDate lastDayOfThisMonth() {
LocalDate today = LocalDate.now();
return today.with(TemporalAdjusters.lastDayOfMonth());
}
/**
* 取本月第一天的开始时间
*/
public static LocalDateTime startOfThisMonth() {
return LocalDateTime.of(firstDayOfThisMonth(), LocalTime.MIN);
}
/**
* 取本月最后一天的结束时间
*/
public static LocalDateTime endOfThisMonth() {
return LocalDateTime.of(lastDayOfThisMonth(), LocalTime.MAX);
}
/**
* 取N天后
*/
public static LocalDateTime plusDays(int days) {
LocalDateTime now = LocalDateTime.now();
return now.plusDays(days);
}
/**
* 取N小时后
*/
public static LocalDateTime plusHours(int hours) {
LocalDateTime now = LocalDateTime.now();
return now.plusHours(hours);
}
/**
* 取N小时后
*/
public static long plusHours(long timestamp, int hours) {
return timestamp + hours * 60 * 60;
}
}
......@@ -28,7 +28,7 @@ import java.time.LocalDateTime;
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@TableName("asset")
@ApiModel(value = "视频", description = "视频1")
@ApiModel(value = "视频", description = "视频")
public class Asset implements Serializable {
private static final long serialVersionUID = 1L;
......@@ -82,4 +82,91 @@ public class Asset implements Serializable {
@TableField(exist = false)
private String assetCopyrightOwnerName;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAssetCopyrightOwnerId() {
return assetCopyrightOwnerId;
}
public void setAssetCopyrightOwnerId(String assetCopyrightOwnerId) {
this.assetCopyrightOwnerId = assetCopyrightOwnerId;
}
public String getAssetTypeId() {
return assetTypeId;
}
public void setAssetTypeId(String assetTypeId) {
this.assetTypeId = assetTypeId;
}
public String getThumbnail() {
return thumbnail;
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
public String getVideoUrl() {
return videoUrl;
}
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public String getAuditStatus() {
return auditStatus;
}
public void setAuditStatus(String auditStatus) {
this.auditStatus = auditStatus;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
public String getAssetTypeName() {
return assetTypeName;
}
public void setAssetTypeName(String assetTypeName) {
this.assetTypeName = assetTypeName;
}
public String getAssetCopyrightOwnerName() {
return assetCopyrightOwnerName;
}
public void setAssetCopyrightOwnerName(String assetCopyrightOwnerName) {
this.assetCopyrightOwnerName = assetCopyrightOwnerName;
}
}
......@@ -28,7 +28,7 @@ import java.time.LocalDateTime;
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@TableName("asset_type")
@ApiModel(value = "视频分类", description = "视频分类1")
@ApiModel(value = "视频分类", description = "视频分类")
public class AssetType implements Serializable {
private static final long serialVersionUID = 1L;
......
package cn.wisenergy.chnmuseum.party.web.controller;
import cn.wisenergy.chnmuseum.party.auth.SHA256PasswordEncryptionService;
import cn.wisenergy.chnmuseum.party.auth.util.JwtTokenUtil;
import cn.wisenergy.chnmuseum.party.common.util.TimeUtils;
import cn.wisenergy.chnmuseum.party.model.Employee;
import cn.wisenergy.chnmuseum.party.service.impl.EmployeeServiceImpl;
import cn.wisenergy.chnmuseum.party.web.controller.base.BaseController;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@RestController("/cmRestApi")
public class ChinaMobileRestApiController extends BaseController {
private static final Logger LOGGER = LoggerFactory.getLogger(ChinaMobileRestApiController.class);
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private EmployeeServiceImpl employeeService;
@Resource
private SysLogController sysLogController;
private static final String SHIRO_JWT_TOKEN = "shiro:jwt:token:";
//用户登录次数计数 redisKey 前缀
private static final String SHIRO_LOGIN_COUNT = "shiro_login_count_";
//用户登录是否被锁定 一小时 redisKey 前缀
private static final String SHIRO_IS_LOCK = "shiro_is_lock_";
/**
* 管理员ajax登录请求 后端用户登录
*
* @param username
* @param password
* @return
*/
@RequestMapping(value = "/user/webLogin", method = RequestMethod.POST)
public ResponseEntity<JSONObject> login(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "boxNo") String boxNo) {
JSONObject resultMap = new JSONObject(true);
Employee employee;
if (StringUtils.isNoneBlank(username)) {
//访问一次,计数一次
ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + username))) {
resultMap.put("status", 400);
resultMap.put("message", "由于密码输入错误次数大于5次,12小时内帐号已禁止登录!请您联系相关管理人员,联系电话:13924551212,邮箱:325346534@zh.com。");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
}
employee = employeeService.selectByUsername(username);
if (employee == null) {
resultMap.put("status", 500);
resultMap.put("message", "用户名或密码不正确!");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
}
if (!employee.getStatus()) {
throw new DisabledAccountException("此帐号已禁用,请联系管理员!");
}
if (!employee.getAllowLogin()) {
throw new DisabledAccountException("您无权访问,请联系管理员!");
}
try {
byte[] salt = employee.getPasswordSalt();
if (!new String(SHA256PasswordEncryptionService.createPasswordHash(password, salt)).equals(new String(employee.getPasswordHash()))) {
opsForValue.increment(SHIRO_LOGIN_COUNT + username, 1);
//计数大于5时,设置用户被锁定一小时
String s = opsForValue.get(SHIRO_LOGIN_COUNT + username);
if (StringUtils.isNotBlank(s)) {
if (Integer.parseInt(s) >= 5) {
opsForValue.set(SHIRO_IS_LOCK + username, "LOCK");
stringRedisTemplate.expire(SHIRO_IS_LOCK + username, 12, TimeUnit.HOURS);
}
}
throw new IncorrectCredentialsException("用户名或密码不正确!");
}
//登录时插入系统日志
String operationContent = username + "登录本系统";
if (employee.getBankBranchName() != null) {
operationContent += ",归属网点" + employee.getBankBranchName();
}
this.sysLogController.insertSysLog(operationContent, username);
String token = JwtTokenUtil.sign(username, employee.getId());
// 将token信息存入Redis
stringRedisTemplate.opsForValue().set(SHIRO_JWT_TOKEN + token, employee.getId(), 240, TimeUnit.MINUTES);
JSONObject jsonObject = new JSONObject(true);
jsonObject.put("token", token);
jsonObject.put("userId", employee.getId());
jsonObject.put("userName", employee.getUsername());
jsonObject.put("expire", TimeUtils.format(LocalDateTime.now().plusMinutes(240), TimeUtils.FORMAT_ONE));
resultMap.put("resultCode", 200);
resultMap.put("message", "成功");
resultMap.put("data", jsonObject);
return ResponseEntity.status(HttpStatus.OK).body(resultMap);
} catch (Exception e) {
resultMap.put("status", 500);
resultMap.put("message", e.getMessage());
}
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
}
@RequestMapping(value = "/user/logout", method = RequestMethod.GET)
public ResponseEntity<JSONObject> logout(@RequestHeader(value = "token") String token) {
try {
if (StringUtils.isNotBlank(token)) {
SecurityUtils.getSubject().logout();
this.stringRedisTemplate.delete(SHIRO_JWT_TOKEN + token);
}
JSONObject resultMap = new JSONObject();
resultMap.put("resultCode", 200);
resultMap.put("message", "成功");
resultMap.put("data", "");
return ResponseEntity.status(HttpStatus.OK).body(resultMap);
} catch (Exception e) {
LOGGER.error("注销错误!", e);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
@ApiOperation(value = "获取单个成员信息")
@GetMapping(value = "/user/getUserInfo")
@RequiresPermissions("/user/getUserInfo")
public ResponseEntity<JSONObject> getById(String userId, @RequestHeader("token") String token) {
try {
Employee employee = employeeService.selectByEmpId(userId);
// BankBranchInfo bankBranch = this.employeeService.getById(Id);
// if (bankBranch != null) {
// employee.setBankBranchName(bankBranch.getName());
// }
if (null == employee) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
JSONObject jsonObject = new JSONObject(true);
jsonObject.put("token", token);
jsonObject.put("userId", employee.getId());
jsonObject.put("userName", employee.getUsername());
long expire = stringRedisTemplate.getExpire(SHIRO_JWT_TOKEN + token) == null ? 0L : stringRedisTemplate.getExpire(SHIRO_JWT_TOKEN + token);
jsonObject.put("expire", TimeUtils.format(LocalDateTime.now().plusMinutes(expire), TimeUtils.FORMAT_ONE));
JSONObject resultMap = new JSONObject();
resultMap.put("resultCode", 200);
resultMap.put("message", "成功");
resultMap.put("data", jsonObject);
return ResponseEntity.ok(resultMap);
} catch (Exception e) {
logger.error("查询成员信息错误!", e);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
......@@ -7,6 +7,7 @@ import cn.wisenergy.chnmuseum.party.model.Employee;
import cn.wisenergy.chnmuseum.party.model.Menu;
import cn.wisenergy.chnmuseum.party.service.impl.EmployeeServiceImpl;
import cn.wisenergy.chnmuseum.party.service.impl.MenuServiceImpl;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
......@@ -180,95 +181,18 @@ public class LoginController {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
}
/**
* 管理员ajax登录请求 后端用户登录
*
* @param username
* @param password
* @return
*/
@RequestMapping(value = "login", method = RequestMethod.POST)
public ResponseEntity<Map<String, Object>> login(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "boxNo") String boxNo,
HttpServletRequest request) {
Map<String, Object> resultMap = new LinkedHashMap<>();
Employee employee;
if (StringUtils.isNoneBlank(username)) {
//访问一次,计数一次
ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + username))) {
resultMap.put("status", 400);
resultMap.put("message", "由于密码输入错误次数大于5次,12小时内帐号已禁止登录!请您联系相关管理人员,联系电话:13924551212,邮箱:325346534@zh.com。");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
}
employee = employeeService.selectByUsername(username);
if (employee == null) {
resultMap.put("status", 500);
resultMap.put("message", "用户名或密码不正确!");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
}
if (!employee.getStatus()) {
throw new DisabledAccountException("此帐号已禁用,请联系管理员!");
}
if (!employee.getAllowLogin()) {
throw new DisabledAccountException("您无权访问,请联系管理员!");
}
try {
byte[] salt = employee.getPasswordSalt();
if (!new String(SHA256PasswordEncryptionService.createPasswordHash(password, salt)).equals(new String(employee.getPasswordHash()))) {
opsForValue.increment(SHIRO_LOGIN_COUNT + username, 1);
//计数大于5时,设置用户被锁定一小时
String s = opsForValue.get(SHIRO_LOGIN_COUNT + username);
if (StringUtils.isNotBlank(s)) {
if (Integer.parseInt(s) >= 5) {
opsForValue.set(SHIRO_IS_LOCK + username, "LOCK");
stringRedisTemplate.expire(SHIRO_IS_LOCK + username, 12, TimeUnit.HOURS);
}
}
throw new IncorrectCredentialsException("用户名或密码不正确!");
}
//获取当前用户角色拥有菜单
List<Menu> userMenuPerms = this.menuService.getUserMenuPerms(employee.getRoleId());
//登录时插入系统日志
String operationContent = username + "登录本系统";
if (employee.getBankBranchName() != null) {
operationContent += ",归属网点" + employee.getBankBranchName();
}
this.sysLogController.insertSysLog(operationContent, username);
String token = JwtTokenUtil.sign(username, employee.getId());
// 将token信息存入Redis
stringRedisTemplate.opsForValue().set(SHIRO_JWT_TOKEN + token, employee.getId(), 240, TimeUnit.MINUTES);
resultMap.put("employee", employee);
resultMap.put("token", token);
resultMap.put("resultCode", 200);
resultMap.put("message", "成功");
return ResponseEntity.status(HttpStatus.OK).body(resultMap);
} catch (Exception e) {
resultMap.put("status", 500);
resultMap.put("message", e.getMessage());
}
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resultMap);
}
@RequestMapping(value = "logout", method = RequestMethod.GET)
public ResponseEntity<Void> logout(@RequestParam(value = "token") String token) {
public ResponseEntity<JSONObject> logout(@RequestHeader(value = "token") String token) {
try {
if (StringUtils.isNotBlank(token)) {
SecurityUtils.getSubject().logout();
this.stringRedisTemplate.delete(SHIRO_JWT_TOKEN + token);
}
return ResponseEntity.ok().build();
JSONObject resultMap = new JSONObject();
resultMap.put("resultCode", 200);
resultMap.put("message", "成功");
resultMap.put("data", "");
return ResponseEntity.status(HttpStatus.OK).body(resultMap);
} catch (Exception e) {
LOGGER.error("注销错误!", e);
}
......@@ -298,7 +222,8 @@ public class LoginController {
}
@RequestMapping(value = {"/verifyCode1"}, method = {RequestMethod.GET}, produces = MediaType.IMAGE_JPEG_VALUE)
public @ResponseBody byte[] verifyCode1(HttpServletRequest request, HttpServletResponse response, Integer width, Integer height) {
public @ResponseBody
byte[] verifyCode1(HttpServletRequest request, HttpServletResponse response, Integer width, Integer height) {
String uuid = UUID.randomUUID().toString();
ByteArrayOutputStream baos = null;
try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment