18 changed files with 448 additions and 179 deletions
			
			
		| @ -0,0 +1,66 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright (c) 2016-2019 人人开源 All rights reserved. | ||||||
|  |  * | ||||||
|  |  * https://www.renren.io
 | ||||||
|  |  * | ||||||
|  |  * 版权所有,侵权必究! | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package com.ruoyi.common.core.page; | ||||||
|  | 
 | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 返回数据 | ||||||
|  |  * | ||||||
|  |  * @author Mark sunlightcs@gmail.com | ||||||
|  |  */ | ||||||
|  | public class R extends HashMap<String, Object> { | ||||||
|  | 	private static final long serialVersionUID = 1L; | ||||||
|  | 	 | ||||||
|  | 	public R() { | ||||||
|  | 		put("code", 0); | ||||||
|  | 		put("msg", "操作成功"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public R(Object o) {} | ||||||
|  | 
 | ||||||
|  | 	public static R error() { | ||||||
|  | 		return error(500, "发生错误,请联系管理员"); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public static R error(String msg) { | ||||||
|  | 		return error(500, msg); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public static R error(int code, String msg) { | ||||||
|  | 		R r = new R(); | ||||||
|  | 		r.put("code", code); | ||||||
|  | 		r.put("msg", msg); | ||||||
|  | 		return r; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public static R ok(String msg) { | ||||||
|  | 		R r = new R(); | ||||||
|  | 		r.put("msg", msg); | ||||||
|  | 		return r; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public static R ok(Map<String, Object> map) { | ||||||
|  | 		R r = new R(); | ||||||
|  | 		r.putAll(map); | ||||||
|  | 		return r; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public static R ok() { | ||||||
|  | 		return new R(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Override | ||||||
|  | 	public R put(String key, Object value) { | ||||||
|  | 		super.put(key, value); | ||||||
|  | 		return this; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,61 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright (c) 2016-2019 人人开源 All rights reserved. | ||||||
|  |  * | ||||||
|  |  * https://www.renren.io
 | ||||||
|  |  * | ||||||
|  |  * 版权所有,侵权必究! | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package com.ruoyi.common.exception; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 自定义异常 | ||||||
|  |  * | ||||||
|  |  * @author Mark sunlightcs@gmail.com | ||||||
|  |  */ | ||||||
|  | public class RYException extends RuntimeException { | ||||||
|  | 	private static final long serialVersionUID = 1L; | ||||||
|  | 
 | ||||||
|  |     private String msg; | ||||||
|  |     private int code = 500; | ||||||
|  | 
 | ||||||
|  |     public RYException(String msg) { | ||||||
|  | 		super(msg); | ||||||
|  | 		this.msg = msg; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public RYException(String msg, Throwable e) { | ||||||
|  | 		super(msg, e); | ||||||
|  | 		this.msg = msg; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public RYException(String msg, int code) { | ||||||
|  | 		super(msg); | ||||||
|  | 		this.msg = msg; | ||||||
|  | 		this.code = code; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public RYException(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; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
|  | } | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | package com.ruoyi.common.utils; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.apache.commons.io.output.CloseShieldOutputStream; | ||||||
|  | import org.springframework.beans.BeansException; | ||||||
|  | import org.springframework.beans.factory.DisposableBean; | ||||||
|  | import org.springframework.context.ApplicationContext; | ||||||
|  | import org.springframework.context.ApplicationContextAware; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | public class BeanUtil implements ApplicationContextAware, DisposableBean { | ||||||
|  |     private static ApplicationContext applicationContext = null; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. | ||||||
|  |      */ | ||||||
|  |     public static <T> T getBean(Class<T> requiredType) { | ||||||
|  |         if(applicationContext==null){ | ||||||
|  |             throw new IllegalStateException("applicaitonContext属性未注入, 请在SpringBoot启动类中注册BeanUtil."); | ||||||
|  |         } | ||||||
|  |         return applicationContext.getBean(requiredType); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void destroy() { | ||||||
|  |         applicationContext = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { | ||||||
|  |         if (BeanUtil.applicationContext != null) { | ||||||
|  |             System.out.println("BeanUtil中的ApplicationContext被覆盖, 原有ApplicationContext为:" + BeanUtil.applicationContext); | ||||||
|  |         } | ||||||
|  |         BeanUtil.applicationContext = applicationContext; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,75 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright (c) 2016-2019 人人开源 All rights reserved. | ||||||
|  |  * | ||||||
|  |  * https://www.renren.io
 | ||||||
|  |  * | ||||||
|  |  * 版权所有,侵权必究! | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package com.ruoyi.common.utils; | ||||||
|  | 
 | ||||||
|  | import com.baomidou.mybatisplus.core.metadata.OrderItem; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||||
|  | import com.ruoyi.common.constant.Constants; | ||||||
|  | import com.ruoyi.common.xss.SQLFilter; | ||||||
|  | 
 | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询参数 | ||||||
|  |  * | ||||||
|  |  * @author Mark sunlightcs@gmail.com | ||||||
|  |  */ | ||||||
|  | public class Query<T> { | ||||||
|  | 
 | ||||||
|  |     public Page<T> getPage(Map<String, Object> params) { | ||||||
|  |         return this.getPage(params, null, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Page<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) { | ||||||
|  |         //分页参数
 | ||||||
|  |         long curPage = 1; | ||||||
|  |         long limit = 10; | ||||||
|  | 
 | ||||||
|  |         if(params.get(Constants.PAGE) != null){ | ||||||
|  |             curPage = Long.parseLong((String)params.get(Constants.PAGE)); | ||||||
|  |         } | ||||||
|  |         if(params.get(Constants.LIMIT) != null){ | ||||||
|  |             limit = Long.parseLong((String)params.get(Constants.LIMIT)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         //分页对象
 | ||||||
|  |         Page<T> page = new Page<>(curPage, limit); | ||||||
|  | 
 | ||||||
|  |         //分页参数
 | ||||||
|  |         params.put(Constants.PAGE, page); | ||||||
|  | 
 | ||||||
|  |         //排序字段
 | ||||||
|  |         //防止SQL注入(因为sidx、order是通过拼接SQL实现排序的,会有SQL注入风险)
 | ||||||
|  |         String orderField = SQLFilter.sqlInject((String)params.get(Constants.ORDER_FIELD)); | ||||||
|  |         String order = (String)params.get(Constants.ORDER); | ||||||
|  | 
 | ||||||
|  |         //前端字段排序
 | ||||||
|  |         if(StringUtils.isNotEmpty(orderField) && StringUtils.isNotEmpty(order)){ | ||||||
|  |             if(Constants.ASC.equalsIgnoreCase(order)) { | ||||||
|  |                 return  page.addOrder(OrderItem.asc(orderField)); | ||||||
|  |             }else { | ||||||
|  |                 return page.addOrder(OrderItem.desc(orderField)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         //没有排序字段,则不排序
 | ||||||
|  |         if(StringUtils.isBlank(defaultOrderField)){ | ||||||
|  |             return page; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         //默认排序
 | ||||||
|  |         if(isAsc) { | ||||||
|  |             page.addOrder(OrderItem.asc(defaultOrderField)); | ||||||
|  |         }else { | ||||||
|  |             page.addOrder(OrderItem.desc(defaultOrderField)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return page; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright (c) 2016-2019 人人开源 All rights reserved. | ||||||
|  |  * | ||||||
|  |  * https://www.renren.io
 | ||||||
|  |  * | ||||||
|  |  * 版权所有,侵权必究! | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package com.ruoyi.common.xss; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import com.ruoyi.common.exception.RYException; | ||||||
|  | import org.apache.commons.lang3.StringUtils; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * SQL过滤 | ||||||
|  |  * | ||||||
|  |  * @author Mark sunlightcs@gmail.com | ||||||
|  |  */ | ||||||
|  | public class SQLFilter { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * SQL注入过滤 | ||||||
|  |      * @param str  待验证的字符串 | ||||||
|  |      */ | ||||||
|  |     public static String sqlInject(String str){ | ||||||
|  |         if(StringUtils.isBlank(str)){ | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         //去掉'|"|;|\字符
 | ||||||
|  |         str = StringUtils.replace(str, "'", ""); | ||||||
|  |         str = StringUtils.replace(str, "\"", ""); | ||||||
|  |         str = StringUtils.replace(str, ";", ""); | ||||||
|  |         str = StringUtils.replace(str, "\\", ""); | ||||||
|  | 
 | ||||||
|  |         //转换成小写
 | ||||||
|  |         str = str.toLowerCase(); | ||||||
|  | 
 | ||||||
|  |         //非法字符
 | ||||||
|  |         String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"}; | ||||||
|  | 
 | ||||||
|  |         //判断是否包含非法字符
 | ||||||
|  |         for(String keyword : keywords){ | ||||||
|  |             if(str.indexOf(keyword) != -1){ | ||||||
|  |                 throw new RYException("包含非法字符"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return str; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,132 +0,0 @@ | |||||||
| package com.ruoyi.framework.config; |  | ||||||
| 
 |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.List; |  | ||||||
| import javax.sql.DataSource; |  | ||||||
| import org.apache.ibatis.io.VFS; |  | ||||||
| import org.apache.ibatis.session.SqlSessionFactory; |  | ||||||
| import org.mybatis.spring.SqlSessionFactoryBean; |  | ||||||
| import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; |  | ||||||
| import org.springframework.context.annotation.Bean; |  | ||||||
| import org.springframework.context.annotation.Configuration; |  | ||||||
| import org.springframework.core.env.Environment; |  | ||||||
| import org.springframework.core.io.DefaultResourceLoader; |  | ||||||
| import org.springframework.core.io.Resource; |  | ||||||
| import org.springframework.core.io.support.PathMatchingResourcePatternResolver; |  | ||||||
| import org.springframework.core.io.support.ResourcePatternResolver; |  | ||||||
| import org.springframework.core.type.classreading.CachingMetadataReaderFactory; |  | ||||||
| import org.springframework.core.type.classreading.MetadataReader; |  | ||||||
| import org.springframework.core.type.classreading.MetadataReaderFactory; |  | ||||||
| import org.springframework.util.ClassUtils; |  | ||||||
| import com.ruoyi.common.utils.StringUtils; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Mybatis支持*匹配扫描包 |  | ||||||
|  *  |  | ||||||
|  * @author ruoyi |  | ||||||
|  */ |  | ||||||
| @Configuration |  | ||||||
| public class MyBatisConfig |  | ||||||
| { |  | ||||||
|     @Autowired |  | ||||||
|     private Environment env; |  | ||||||
| 
 |  | ||||||
|     static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; |  | ||||||
| 
 |  | ||||||
|     public static String setTypeAliasesPackage(String typeAliasesPackage) |  | ||||||
|     { |  | ||||||
|         ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); |  | ||||||
|         MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); |  | ||||||
|         List<String> allResult = new ArrayList<String>(); |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             for (String aliasesPackage : typeAliasesPackage.split(",")) |  | ||||||
|             { |  | ||||||
|                 List<String> result = new ArrayList<String>(); |  | ||||||
|                 aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX |  | ||||||
|                         + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; |  | ||||||
|                 Resource[] resources = resolver.getResources(aliasesPackage); |  | ||||||
|                 if (resources != null && resources.length > 0) |  | ||||||
|                 { |  | ||||||
|                     MetadataReader metadataReader = null; |  | ||||||
|                     for (Resource resource : resources) |  | ||||||
|                     { |  | ||||||
|                         if (resource.isReadable()) |  | ||||||
|                         { |  | ||||||
|                             metadataReader = metadataReaderFactory.getMetadataReader(resource); |  | ||||||
|                             try |  | ||||||
|                             { |  | ||||||
|                                 result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); |  | ||||||
|                             } |  | ||||||
|                             catch (ClassNotFoundException e) |  | ||||||
|                             { |  | ||||||
|                                 e.printStackTrace(); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if (result.size() > 0) |  | ||||||
|                 { |  | ||||||
|                     HashSet<String> hashResult = new HashSet<String>(result); |  | ||||||
|                     allResult.addAll(hashResult); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             if (allResult.size() > 0) |  | ||||||
|             { |  | ||||||
|                 typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         catch (IOException e) |  | ||||||
|         { |  | ||||||
|             e.printStackTrace(); |  | ||||||
|         } |  | ||||||
|         return typeAliasesPackage; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public Resource[] resolveMapperLocations(String[] mapperLocations) |  | ||||||
|     { |  | ||||||
|         ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); |  | ||||||
|         List<Resource> resources = new ArrayList<Resource>(); |  | ||||||
|         if (mapperLocations != null) |  | ||||||
|         { |  | ||||||
|             for (String mapperLocation : mapperLocations) |  | ||||||
|             { |  | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|                     Resource[] mappers = resourceResolver.getResources(mapperLocation); |  | ||||||
|                     resources.addAll(Arrays.asList(mappers)); |  | ||||||
|                 } |  | ||||||
|                 catch (IOException e) |  | ||||||
|                 { |  | ||||||
|                     // ignore
 |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return resources.toArray(new Resource[resources.size()]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Bean |  | ||||||
|     public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception |  | ||||||
|     { |  | ||||||
|         String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); |  | ||||||
|         String mapperLocations = env.getProperty("mybatis.mapperLocations"); |  | ||||||
|         String configLocation = env.getProperty("mybatis.configLocation"); |  | ||||||
|         typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); |  | ||||||
|         VFS.addImplClass(SpringBootVFS.class); |  | ||||||
| 
 |  | ||||||
|         final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); |  | ||||||
|         sessionFactory.setDataSource(dataSource); |  | ||||||
|         sessionFactory.setTypeAliasesPackage(typeAliasesPackage); |  | ||||||
|         sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); |  | ||||||
|         sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); |  | ||||||
|         return sessionFactory.getObject(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -0,0 +1,62 @@ | |||||||
|  | package com.ruoyi.framework.config; | ||||||
|  | 
 | ||||||
|  | import com.baomidou.mybatisplus.annotation.DbType; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Mybatis Plus 配置 | ||||||
|  |  * | ||||||
|  |  * @author ruoyi | ||||||
|  |  */ | ||||||
|  | @EnableTransactionManagement(proxyTargetClass = true) | ||||||
|  | @Configuration | ||||||
|  | public class MybatisPlusConfig | ||||||
|  | { | ||||||
|  |     @Bean | ||||||
|  |     public MybatisPlusInterceptor mybatisPlusInterceptor() | ||||||
|  |     { | ||||||
|  |         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); | ||||||
|  |         // 分页插件
 | ||||||
|  |         interceptor.addInnerInterceptor(paginationInnerInterceptor()); | ||||||
|  |         // 乐观锁插件
 | ||||||
|  |         interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); | ||||||
|  |         // 阻断插件
 | ||||||
|  |         interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); | ||||||
|  |         return interceptor; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html
 | ||||||
|  |      */ | ||||||
|  |     public PaginationInnerInterceptor paginationInnerInterceptor() | ||||||
|  |     { | ||||||
|  |         PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); | ||||||
|  |         // 设置数据库类型为mysql
 | ||||||
|  |         paginationInnerInterceptor.setDbType(DbType.MYSQL); | ||||||
|  |         // 设置最大单页限制数量,默认 500 条,-1 不受限制
 | ||||||
|  |         paginationInnerInterceptor.setMaxLimit(-1L); | ||||||
|  |         return paginationInnerInterceptor; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html
 | ||||||
|  |      */ | ||||||
|  |     public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() | ||||||
|  |     { | ||||||
|  |         return new OptimisticLockerInnerInterceptor(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html
 | ||||||
|  |      */ | ||||||
|  |     public BlockAttackInnerInterceptor blockAttackInnerInterceptor() | ||||||
|  |     { | ||||||
|  |         return new BlockAttackInnerInterceptor(); | ||||||
|  |     } | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in new issue