Commit 3d74dac9 authored by 郑杰's avatar 郑杰
Browse files

eladmin 1.0 版本发布

parents
package me.zhengjie.monitor.rest;
import me.zhengjie.monitor.domain.Logging;
import me.zhengjie.monitor.service.query.LoggingQueryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author jie
* @date 2018-11-24
*/
@RestController
@RequestMapping("api")
public class LoggingController {
@Autowired
private LoggingQueryService loggingQueryService;
@GetMapping(value = "logs")
@PreAuthorize("hasAnyRole('ADMIN','USER')")
public ResponseEntity getLogs(Logging logging, Pageable pageable){
return new ResponseEntity(loggingQueryService.queryAll(logging,pageable), HttpStatus.OK);
}
}
package me.zhengjie.monitor.rest;
import me.zhengjie.common.aop.log.Log;
import me.zhengjie.monitor.domain.vo.RedisVo;
import me.zhengjie.monitor.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* @author jie
* @date 2018-12-10
*/
@RestController
@RequestMapping("api")
public class RedisController {
@Autowired
private RedisService redisService;
@Log(description = "查询Redis缓存")
@GetMapping(value = "/redis")
@PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_SELECT')")
public ResponseEntity getRedis(String key, Pageable pageable){
return new ResponseEntity(redisService.findByKey(key,pageable), HttpStatus.OK);
}
@Log(description = "新增Redis缓存")
@PostMapping(value = "/redis")
@PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_CREATE')")
public ResponseEntity create(@Validated @RequestBody RedisVo resources){
redisService.save(resources);
return new ResponseEntity(HttpStatus.CREATED);
}
@Log(description = "修改Redis缓存")
@PutMapping(value = "/redis")
@PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_EDIT')")
public ResponseEntity update(@Validated @RequestBody RedisVo resources){
redisService.save(resources);
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
@Log(description = "删除Redis缓存")
@DeleteMapping(value = "/redis/{key}")
@PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_DELETE')")
public ResponseEntity delete(@PathVariable String key){
redisService.delete(key);
return new ResponseEntity(HttpStatus.OK);
}
@Log(description = "清空Redis缓存")
@DeleteMapping(value = "/redis/all")
@PreAuthorize("hasAnyRole('ADMIN','REDIS_ALL','REDIS_DELETE')")
public ResponseEntity deleteAll(){
redisService.flushdb();
return new ResponseEntity(HttpStatus.OK);
}
}
package me.zhengjie.monitor.rest;
import me.zhengjie.common.utils.RequestHolder;
import me.zhengjie.monitor.service.VisitsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author jie
* @date 2018-12-13
*/
@RestController
@RequestMapping("api")
public class VisitsController {
@Autowired
private VisitsService visitsService;
@PostMapping(value = "/visits")
public ResponseEntity create(){
visitsService.save(RequestHolder.getHttpServletRequest());
return new ResponseEntity(HttpStatus.CREATED);
}
@GetMapping(value = "/visits")
public ResponseEntity get(){
return new ResponseEntity(visitsService.get(),HttpStatus.OK);
}
@GetMapping(value = "/visits/chartData")
public ResponseEntity getChartData(){
return new ResponseEntity(visitsService.getChartData(),HttpStatus.OK);
}
}
package me.zhengjie.monitor.service;
import me.zhengjie.monitor.domain.Logging;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.scheduling.annotation.Async;
/**
* @author jie
* @date 2018-11-24
*/
public interface LoggingService {
/**
* 新增日志
* @param joinPoint
* @param logging
*/
@Async
void save(ProceedingJoinPoint joinPoint, Logging logging);
}
package me.zhengjie.monitor.service;
import me.zhengjie.monitor.domain.vo.RedisVo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* 可自行扩展
* @author jie
* @date 2018-12-10
*/
public interface RedisService {
/**
* findById
* @param key
* @return
*/
public Page findByKey(String key, Pageable pageable);
/**
* create
* @param redisVo
*/
public void save(RedisVo redisVo);
/**
* delete
* @param key
*/
public void delete(String key);
/**
* 清空所有缓存
*/
public void flushdb();
}
package me.zhengjie.monitor.service;
import org.springframework.scheduling.annotation.Async;
import javax.servlet.http.HttpServletRequest;
/**
* @author jie
* @date 2018-12-13
*/
public interface VisitsService {
/**
* 新增记录
* @param request
*/
@Async
void save(HttpServletRequest request);
/**
* 获取数据
* @return
*/
Object get();
/**
* getChartData
* @return
*/
Object getChartData();
}
package me.zhengjie.monitor.service.impl;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import me.zhengjie.common.utils.IpUtil;
import me.zhengjie.common.utils.RequestHolder;
import me.zhengjie.core.security.AuthorizationUser;
import me.zhengjie.core.utils.JwtTokenUtil;
import me.zhengjie.monitor.domain.Logging;
import me.zhengjie.monitor.repository.LoggingRepository;
import me.zhengjie.monitor.service.LoggingService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* @author jie
* @date 2018-11-24
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class LoggingServiceImpl implements LoggingService {
@Autowired
private LoggingRepository loggingRepository;
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
private JwtTokenUtil jwtTokenUtil;
private final String LOGINPATH = "authenticationLogin";
@Override
@Transactional(rollbackFor = Exception.class)
public void save(ProceedingJoinPoint joinPoint, Logging logging){
// 获取request
HttpServletRequest request = RequestHolder.getHttpServletRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
me.zhengjie.common.aop.log.Log log = method.getAnnotation(me.zhengjie.common.aop.log.Log.class);
// 描述
if (log != null) {
logging.setDescription(log.description());
}
// 方法路径
String methodName = joinPoint.getTarget().getClass().getName()+"."+signature.getName()+"()";
String params = "{";
//参数值
Object[] argValues = joinPoint.getArgs();
//参数名称
String[] argNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
// 用户名
String username = "";
if(argValues != null){
for (int i = 0; i < argValues.length; i++) {
params += " " + argNames[i] + ": " + argValues[i];
}
}
// 获取IP地址
logging.setRequestIp(IpUtil.getIP(request));
if(!LOGINPATH.equals(signature.getName())){
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
username = userDetails.getUsername();
} else {
AuthorizationUser user = JSONUtil.toBean(new JSONObject(argValues[0]),AuthorizationUser.class);
username = user.getUsername();
}
logging.setMethod(methodName);
logging.setUsername(username);
logging.setParams(params + " }");
loggingRepository.save(logging);
}
}
package me.zhengjie.monitor.service.impl;
import me.zhengjie.common.utils.PageUtil;
import me.zhengjie.monitor.domain.vo.RedisVo;
import me.zhengjie.monitor.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.*;
/**
* @author jie
* @date 2018-12-10
*/
@Service
public class RedisServiceImpl implements RedisService {
@Autowired
JedisPool pool;
@Override
public Page findByKey(String key, Pageable pageable){
Jedis jedis = null;
try{
jedis = pool.getResource();
List<RedisVo> redisVos = new ArrayList<>();
if(!key.equals("*")){
key = "*" + key + "*";
}
for (String s : jedis.keys(key)) {
RedisVo redisVo = new RedisVo(s,jedis.get(s));
redisVos.add(redisVo);
}
Page<RedisVo> page = new PageImpl<RedisVo>(
PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(),redisVos),
pageable,
redisVos.size());
return page;
}finally{
if(null != jedis){
jedis.close(); // 释放资源还给连接池
}
}
}
@Override
public void save(RedisVo redisVo) {
Jedis jedis = null;
try{
jedis = pool.getResource();
jedis.set(redisVo.getKey(),redisVo.getValue());
}finally{
if(null != jedis){
jedis.close(); // 释放资源还给连接池
}
}
}
@Override
public void delete(String key) {
Jedis jedis = null;
try{
jedis = pool.getResource();
jedis.del(key);
}finally{
if(null != jedis){
jedis.close(); // 释放资源还给连接池
}
}
}
@Override
public void flushdb() {
Jedis jedis = null;
try{
jedis = pool.getResource();
jedis.flushDB();
}finally{
if(null != jedis){
jedis.close(); // 释放资源还给连接池
}
}
}
}
package me.zhengjie.monitor.service.impl;
import me.zhengjie.common.utils.IpUtil;
import me.zhengjie.common.utils.RequestHolder;
import me.zhengjie.common.utils.TimeUtil;
import me.zhengjie.monitor.domain.Logging;
import me.zhengjie.monitor.domain.Visits;
import me.zhengjie.monitor.repository.LoggingRepository;
import me.zhengjie.monitor.repository.VisitsRepository;
import me.zhengjie.monitor.service.VisitsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author jie
* @date 2018-12-13
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class VisitsServiceImpl implements VisitsService {
@Autowired
private VisitsRepository visitsRepository;
@Autowired
private LoggingRepository loggingRepository;
@Override
public void save(HttpServletRequest request) {
LocalDate localDate = LocalDate.now();
Visits visits = visitsRepository.findByDate(localDate.toString());
if(visits != null){
visits.setPvCounts(visits.getPvCounts()+1);
long ipCounts = loggingRepository.findIp(localDate.toString(), localDate.plusDays(1).toString());
visits.setIpCounts(ipCounts);
}else {
visits = new Visits();
visits.setWeekDay(TimeUtil.getWeekDay());
visits.setPvCounts(1L);
visits.setIpCounts(1L);
visits.setDate(localDate.toString());
}
visitsRepository.save(visits);
}
@Override
public Object get() {
Map map = new HashMap();
LocalDate localDate = LocalDate.now();
Visits visits = visitsRepository.findByDate(localDate.toString());
if(visits == null){
save(RequestHolder.getHttpServletRequest());
}
List<Visits> list = visitsRepository.findAllVisits(localDate.minusDays(6).toString(),localDate.plusDays(1).toString());
long recentVisits = 0, recentIp = 0;
for (Visits data : list) {
recentVisits += data.getPvCounts();
recentIp += data.getIpCounts();
}
map.put("newVisits",visits.getPvCounts());
map.put("newIp",visits.getIpCounts());
map.put("recentVisits",recentVisits);
map.put("recentIp",recentIp);
return map;
}
@Override
public Object getChartData() {
Map map = new HashMap();
LocalDate localDate = LocalDate.now();
List<Visits> list = visitsRepository.findAllVisits(localDate.minusDays(6).toString(),localDate.plusDays(1).toString());
map.put("weekDays",list.stream().map(Visits::getWeekDay).collect(Collectors.toList()));
map.put("visitsData",list.stream().map(Visits::getPvCounts).collect(Collectors.toList()));
map.put("ipData",list.stream().map(Visits::getIpCounts).collect(Collectors.toList()));
return map;
}
}
package me.zhengjie.monitor.service.query;
import me.zhengjie.monitor.domain.Logging;
import me.zhengjie.monitor.repository.LoggingRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
/**
* @author jie
* @date 2018-11-24
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class LoggingQueryService {
@Autowired
private LoggingRepository loggingRepository;
public Page queryAll(Logging logging, Pageable pageable){
return loggingRepository.findAll(new Spec(logging),pageable);
}
public List queryAll(Logging logging){
return loggingRepository.findAll(new Spec(logging));
}
class Spec implements Specification<Logging> {
private Logging logging;
public Spec(Logging logging){
this.logging = logging;
}
@Override
public Predicate toPredicate(Root<Logging> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
List<Predicate> list = new ArrayList<Predicate>();
if(!ObjectUtils.isEmpty(logging.getUsername())){
list.add(cb.like(root.get("username").as(String.class),"%"+logging.getUsername()+"%"));
}
if (!ObjectUtils.isEmpty(logging.getLogType())) {
list.add(cb.equal(root.get("logType").as(String.class), logging.getLogType()));
}
Predicate[] p = new Predicate[list.size()];
return cb.and(list.toArray(p));
}
}
}
package me.zhengjie.system.domain;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.sql.Timestamp;
import java.util.Set;
/**
* @author jie
* @date 2018-12-17
*/
@Entity
@Getter
@Setter
@Table(name = "menu")
public class Menu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String name;
@Column(unique = true)
private Long soft;
@Column(name = "path")
private String path;
private String component;
private String icon;
/**
* 上级菜单ID
*/
@Column(name = "pid",nullable = false)
private Long pid;
/**
* 是否为外链 true/false
*/
private Boolean iFrame;
@ManyToMany
@JoinTable(name = "menus_roles", joinColumns = {@JoinColumn(name = "menu_id",referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
private Set<Role> roles;
@CreationTimestamp
private Timestamp createTime;
}
package me.zhengjie.system.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Set;
/**
* @author jie
* @date 2018-12-03
*/
@Entity
@Getter
@Setter
@Table(name = "permission")
public class Permission implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String name;
/**
* 上级类目
*/
@NotNull
@Column(name = "pid",nullable = false)
private Long pid;
@NotBlank
private String alias;
@JsonIgnore
@ManyToMany(mappedBy = "permissions")
private Set<Role> roles;
@CreationTimestamp
private Timestamp createTime;
@Override
public String toString() {
return "Permission{" +
"id=" + id +
", name='" + name + '\'' +
", pid=" + pid +
", alias='" + alias + '\'' +
", createTime=" + createTime +
'}';
}
}
package me.zhengjie.system.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Set;
/**
* 角色
* @author jie
* @date 2018-11-22
*/
@Entity
@Table(name = "role")
@Getter
@Setter
public class Role implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
@NotBlank
private String name;
@Column
private String remark;
@JsonIgnore
@ManyToMany(mappedBy = "roles")
private Set<User> users;
@ManyToMany
@JoinTable(name = "roles_permissions", joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "permission_id",referencedColumnName = "id")})
private Set<Permission> permissions;
@JsonIgnore
@ManyToMany(mappedBy = "roles")
private Set<Menu> menus;
@Column
@CreationTimestamp
private Timestamp createTime;
@Override
public String toString() {
return "Role{" +
"id=" + id +
", name='" + name + '\'' +
", remark='" + remark + '\'' +
", createDateTime=" + createTime +
'}';
}
}
package me.zhengjie.system.domain;
import java.io.Serializable;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Set;
/**
* @author jie
* @date 2018-11-22
*/
@Entity
@Getter
@Setter
@Table(name="user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String username;
private String avatar;
@NotBlank
@Pattern(regexp = "([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}",message = "格式错误")
private String email;
@NotNull
private Boolean enabled;
private String password;
@CreationTimestamp
private Timestamp createTime;
private Date lastPasswordResetTime;
@ManyToMany
@JoinTable(name = "users_roles", joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
private Set<Role> roles;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", avatar='" + avatar + '\'' +
", email='" + email + '\'' +
", enabled=" + enabled +
", password='" + password + '\'' +
", createTime=" + createTime +
", lastPasswordResetTime=" + lastPasswordResetTime +
'}';
}
}
\ No newline at end of file
package me.zhengjie.system.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author jie
* @date 2018-12-20
*/
@Data
@AllArgsConstructor
public class MenuMetaVo {
private String title;
private String icon;
}
package me.zhengjie.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.List;
/**
* 构建前端路由时用到
* @author jie
* @date 2018-12-20
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class MenuVo {
private String name;
private String path;
private String component;
private Boolean alwaysShow;
private MenuMetaVo meta;
private List<MenuVo> children;
}
package me.zhengjie.system.repository;
import me.zhengjie.system.domain.Menu;
import me.zhengjie.system.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
import java.util.Set;
/**
* @author jie
* @date 2018-12-17
*/
public interface MenuRepository extends JpaRepository<Menu, Long>, JpaSpecificationExecutor {
/**
* findByName
* @param name
* @return
*/
Menu findByName(String name);
/**
* findByRoles
* @param roleSet
* @return
*/
Set<Menu> findByRoles(Set<Role> roleSet);
/**
* findByPid
* @param pid
* @return
*/
List<Menu> findByPid(long pid);
}
package me.zhengjie.system.repository;
import me.zhengjie.system.domain.Permission;
import me.zhengjie.system.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
import java.util.Set;
/**
* @author jie
* @date 2018-12-03
*/
public interface PermissionRepository extends JpaRepository<Permission, Long>, JpaSpecificationExecutor {
/**
* findByName
* @param name
* @return
*/
Permission findByName(String name);
/**
* findByRoles
* @param roleSet
* @return
*/
Set<Permission> findByRoles(Set<Role> roleSet);
/**
* findByPid
* @param pid
* @return
*/
List<Permission> findByPid(long pid);
}
package me.zhengjie.system.repository;
import me.zhengjie.system.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author jie
* @date 2018-12-03
*/
public interface RoleRepository extends JpaRepository<Role, Long>, JpaSpecificationExecutor {
/**
* findByName
* @param name
* @return
*/
Role findByName(String name);
}
package me.zhengjie.system.repository;
import me.zhengjie.system.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* @author jie
* @date 2018-11-22
*/
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor {
/**
* findByUsername
* @param username
* @return
*/
User findByUsername(String username);
/**
* findByEmail
* @param email
* @return
*/
User findByEmail(String email);
}
Supports Markdown
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