diff --git a/admin/pom.xml b/admin/pom.xml deleted file mode 100644 index 33df279..0000000 --- a/admin/pom.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - yxk_canvasScreen - com.canvas.web - 1.0-SNAPSHOT - - 4.0.0 - - admin - 启动入口 - - - 17 - 17 - - - - - - - - com.canvas.web - system - 1.0-SNAPSHOT - - - - com.canvas.web - generator - 1.0-SNAPSHOT - - - - com.canvas.web - quartz - 1.0-SNAPSHOT - - - - com.canvas.web - queue - 1.0-SNAPSHOT - - - - - - org.projectlombok - lombok - true - - - - org.springframework.amqp - spring-rabbit-test - test - - - - com.alibaba - druid-spring-boot-starter - 1.2.8 - - - - log4j - log4j - 1.2.17 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - 2.1.11.RELEASE - - JavaWeb_Vue - - - - - repackage - - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file diff --git a/admin/src/main/java/com/canvas/web/AdminApplication.java b/admin/src/main/java/com/canvas/web/AdminApplication.java deleted file mode 100644 index 65cfe35..0000000 --- a/admin/src/main/java/com/canvas/web/AdminApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.canvas.web; - - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -@SpringBootApplication(scanBasePackages = {"com.canvas.*"}) -@EnableTransactionManagement -@EnableScheduling -public class AdminApplication { - - public static void main(String[] args){ - SpringApplication.run(AdminApplication.class,args); - System.out.println("多媒体后台管理系统启动成功!"); - } -} diff --git a/admin/src/main/java/com/canvas/web/controller/TestController.java b/admin/src/main/java/com/canvas/web/controller/TestController.java deleted file mode 100644 index 575496b..0000000 --- a/admin/src/main/java/com/canvas/web/controller/TestController.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.canvas.web.controller; - - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -//业务测试接口 -@RestController -@RequestMapping("/test") -public class TestController { -} diff --git a/admin/src/main/resources/application-dev.yml b/admin/src/main/resources/application-dev.yml deleted file mode 100644 index 1b6584d..0000000 --- a/admin/src/main/resources/application-dev.yml +++ /dev/null @@ -1,126 +0,0 @@ -#自定义配置 -canvasweb: - image-url: https://images.canvase.com/ - app-debug: true - - - -spring: - # 配置数据源 - datasource: - # 使用阿里的Druid连接池 - druid: - db-type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy - url: jdbc:mysql://192.168.99.207:3306/javaweb.vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&useSSL=true&tinyInt1isBit=false - username: root - password: ftzn83560792 - - - # 连接池的配置信息 - # 初始连接数 - initialSize: 5 - # 最小连接池数量 - minIdle: 5 - # 最大连接池数量 - maxActive: 20 - # 配置获取连接等待超时的时间 - maxWait: 60000 - # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 - timeBetweenEvictionRunsMillis: 60000 - # 配置一个连接在池中最小生存的时间,单位是毫秒 - minEvictableIdleTimeMillis: 300000 - # 配置一个连接在池中最大生存的时间,单位是毫秒 - maxEvictableIdleTimeMillis: 900000 - # 配置检测连接是否有效 - validationQuery: SELECT 1 FROM DUAL - testWhileIdle: true - testOnBorrow: false - testOnReturn: false - # 打开PSCache,并且指定每个连接上PSCache的大小 - poolPreparedStatements: true - maxPoolPreparedStatementPerConnectionSize: 20 - # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 - filters: stat,wall,log4j - # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 - connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 - # 配置DruidStatFilter - webStatFilter: - enabled: true - url-pattern: "/*" - exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" - # 配置DruidStatViewServlet - statViewServlet: - url-pattern: "/druid/*" - # IP白名单(没有配置或者为空,则允许所有访问) - allow: 127.0.0.1,192.168.163.1 - # IP黑名单 (存在共同时,deny优先于allow) - deny: 192.168.1.73 - # 禁用HTML页面上的“Reset All”功能 - reset-enable: false - # 登录名 - login-username: admin - # 登录密码 - login-password: 123456 - - # Redis数据源 - redis: - # 缓存库默认索引0 - database: 0 - # Redis服务器地址 - host: 127.0.0.1 - # Redis服务器连接端口 - port: 6379 - # Redis服务器连接密码(默认为空) - password: - # 连接超时时间(毫秒) - timeout: 6000 - # 默认的数据过期时间,主要用于shiro权限管理 - expire: 2592000 - jedis: - pool: - max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) - max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) - max-idle: 10 # 连接池中的最大空闲连接 - min-idle: 1 # 连接池中的最小空闲连接 - -file: - #上传的服务器上的映射文件夹 - accessPath: /uploads/ - #静态资源对外暴露的访问路径 - staticAccessPath: /** - #静态资源实际存储路径 - uploadFolder: E:\JavaWeb_Vue\JavaWeb\uploads\ - - -# Shiro -shiro: - cipher-key: f/SX5TIve5WWzT4aQlABJA== - cookie-name: shiro-cookie2 - user: - # 登录地址 - loginUrl: /login - # 权限认证失败地址 - unauthorizedUrl: /unauth - # 首页地址 - indexUrl: /index - # 验证码开关 - captchaEnabled: true - # 验证码类型 math 数组计算 char 字符 - captchaType: math - cookie: - # 设置Cookie的域名 默认空,即当前访问的域名 - domain: - # 设置cookie的有效访问路径 - path: / - # 设置HttpOnly属性 - httpOnly: true - # 设置Cookie的过期时间,天为单位 - maxAge: 30 - session: - # Session超时时间(默认30分钟) - expireTime: 300 - # 同步session到数据库的周期(默认1分钟) - dbSyncPeriod: 1 - # 相隔多久检查一次session的有效性,默认就是10分钟 - validationInterval: 10 \ No newline at end of file diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml deleted file mode 100644 index 6c6f659..0000000 --- a/admin/src/main/resources/application.yml +++ /dev/null @@ -1,27 +0,0 @@ -server: - port: 9030 - servlet: - context-path: /api - -spring: - profiles: - active: dev - - #配置 Jpa - jpa: - properties: - hibernate: - ddl-auto: none - open-in-view: true - - -task: - pool: - # 核心线程池大小 - core-pool-size: 10 - # 最大线程数 - max-pool-size: 30 - # 活跃时间 - keep-alive-seconds: 60 - # 队列容量 - queue-capacity: 50 \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml index f52e2f4..25dcf73 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -13,22 +13,12 @@ 公共模块 - 17 - 17 + 5.3.4 - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-web - + org.springframework.boot @@ -40,11 +30,7 @@ lombok true - - com.alibaba - fastjson - 1.2.79 - + org.apache.commons commons-lang3 @@ -58,12 +44,7 @@ org.springframework.boot spring-boot-starter-aop - - - eu.bitwalker - UserAgentUtils - 1.21 - + com.alibaba @@ -104,17 +85,7 @@ commons-compress 1.21 - - - io.jsonwebtoken - jjwt - 0.9.1 - - - com.auth0 - java-jwt - 3.18.2 - + com.google.zxing @@ -132,6 +103,18 @@ mysql-connector-java runtime + + + + cn.hutool + hutool-all + ${hutool.version} + + + org.hibernate.validator + hibernate-validator + 6.1.5.Final + \ No newline at end of file diff --git a/common/src/main/java/com/canvas/web/annotation/AnonymousAccess.java b/common/src/main/java/com/canvas/web/annotation/AnonymousAccess.java new file mode 100644 index 0000000..1033bcb --- /dev/null +++ b/common/src/main/java/com/canvas/web/annotation/AnonymousAccess.java @@ -0,0 +1,11 @@ +package com.canvas.web.annotation; + + +import java.lang.annotation.*; + +@Inherited +@Documented +@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnonymousAccess { +} diff --git a/common/src/main/java/com/canvas/web/annotation/Query.java b/common/src/main/java/com/canvas/web/annotation/Query.java new file mode 100644 index 0000000..8767d15 --- /dev/null +++ b/common/src/main/java/com/canvas/web/annotation/Query.java @@ -0,0 +1,59 @@ +package com.canvas.web.annotation; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Query { + + //基本对象的属性名 + String propName() default ""; + + //查询方式 + Type type() default Type.EQUAL; + + //连接查询的属性名,如User类中的dept + String joinName() default ""; + + //默认左连接 + Join join() default Join.LEFT; + + //多字段模糊搜索,仅支持String类型字段,多个用逗号隔开,例如:@Query(blurry="email,username") + String blurry() default ""; + + enum Type { + // 相等 + EQUAL + // 大于等于 + , GREATER_THAN + // 小于等于 + , LESS_THAN + // 中模糊查询 + , INNER_LIKE + // 左模糊查询 + , LEFT_LIKE + // 右模糊查询 + , RIGHT_LIKE + // 小于 + , LESS_THAN_NQ + // 包含 + , IN + // 不等于 + ,NOT_EQUAL + // between + ,BETWEEN + // 不为空 + ,NOT_NULL + // 为空 + ,IS_NULL + } + //适用于简单连接查询,复杂的查询适用sql或其他注解 + enum Join { + + LEFT, RIGHT, INNER + } +} diff --git a/common/src/main/java/com/canvas/web/annotation/rest/AnonymousGetMapping.java b/common/src/main/java/com/canvas/web/annotation/rest/AnonymousGetMapping.java new file mode 100644 index 0000000..551167b --- /dev/null +++ b/common/src/main/java/com/canvas/web/annotation/rest/AnonymousGetMapping.java @@ -0,0 +1,59 @@ +package com.canvas.web.annotation.rest; + +import com.canvas.web.annotation.AnonymousAccess; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.GET) +public @interface AnonymousGetMapping { + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) + String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + * + * @since 4.3.5 + */ + @AliasFor(annotation = RequestMapping.class) + String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) + String[] produces() default {}; +} diff --git a/common/src/main/java/com/canvas/web/base/BaseDTO.java b/common/src/main/java/com/canvas/web/base/BaseDTO.java new file mode 100644 index 0000000..6b26def --- /dev/null +++ b/common/src/main/java/com/canvas/web/base/BaseDTO.java @@ -0,0 +1,38 @@ +package com.canvas.web.base; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.sql.Timestamp; + + +@Getter +@Setter +public class BaseDTO implements Serializable { + + private String createBy; + + private String updatedBy; + + private Timestamp createTime; + + private Timestamp updateTime; + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + Field[] fields = this.getClass().getDeclaredFields(); + try { + for (Field f : fields) { + f.setAccessible(true); + builder.append(f.getName(), f.get(this)).append("\n"); + } + } catch (Exception e) { + builder.append("toString builder encounter an error"); + } + return builder.toString(); + } +} diff --git a/common/src/main/java/com/canvas/web/config/CommonConfig.java b/common/src/main/java/com/canvas/web/config/CommonConfig.java new file mode 100644 index 0000000..cf5d64b --- /dev/null +++ b/common/src/main/java/com/canvas/web/config/CommonConfig.java @@ -0,0 +1,41 @@ +package com.canvas.web.config; + + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class CommonConfig { + + /** + * 图片域名 + */ + public static String imageURL; + + /** + * 是否演示环境:true是,false否 + */ + public static boolean appDebug; + + /** + * 图片域名赋值 + * + * @param url 域名地址 + */ + @Value("${javaweb.image-url}") + public void setImageURL(String url) { + imageURL = url; + } + + /** + * 是否演示模式 + * + * @param debug + */ + @Value("${javaweb.app-debug}") + public void setAppDebug(boolean debug) { + appDebug = debug; + } +} diff --git a/common/src/main/java/com/canvas/web/config/UploadFileConfig.java b/common/src/main/java/com/canvas/web/config/UploadFileConfig.java new file mode 100644 index 0000000..2d4a7b5 --- /dev/null +++ b/common/src/main/java/com/canvas/web/config/UploadFileConfig.java @@ -0,0 +1,38 @@ +package com.canvas.web.config; + + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class UploadFileConfig { + /** + * 上传目录 + */ + public static String uploadFolder; + /** + * 访问路径 + */ + public static String staticAccessPath; + /** + * 上传服务器的映射文件夹 + */ + public static String accessPath; + + @Value("${file.uploadFolder}") + public void setUploadFolder(String path) { + uploadFolder = path; + } + + @Value("${file.staticAccessPath}") + public void setStaticAccessPath(String path) { + staticAccessPath = path; + } + + @Value("${file.accessPath}") + public void setAccessPath(String path) { + accessPath = path; + } +} diff --git a/common/src/main/java/com/canvas/web/service/IUplpadService.java b/common/src/main/java/com/canvas/web/service/IUplpadService.java new file mode 100644 index 0000000..d4ed234 --- /dev/null +++ b/common/src/main/java/com/canvas/web/service/IUplpadService.java @@ -0,0 +1,19 @@ +package com.canvas.web.service; + + +import com.canvas.web.utils.JsonResult; + +import javax.servlet.http.HttpServletRequest; + +public interface IUplpadService { + + /** + * 上传图片 + * + * @param request 网络请求 + * @param name 目录名 + * @return + */ + JsonResult uploadImage(HttpServletRequest request, String name); + +} diff --git a/common/src/main/java/com/canvas/web/service/impl/UploadServiceImpl.java b/common/src/main/java/com/canvas/web/service/impl/UploadServiceImpl.java new file mode 100644 index 0000000..affa271 --- /dev/null +++ b/common/src/main/java/com/canvas/web/service/impl/UploadServiceImpl.java @@ -0,0 +1,30 @@ +package com.canvas.web.service.impl; + +import com.canvas.web.service.IUplpadService; +import com.canvas.web.utils.CommonUtils; +import com.canvas.web.utils.JsonResult; +import com.canvas.web.utils.UploadUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; + +public class UploadServiceImpl implements IUplpadService { + + + /** + * 上传图片 + * + * @param request 网络请求 + * @param name 目录名 + * @return + */ + @Override + public JsonResult uploadImage(HttpServletRequest request, String name) { + UploadUtils uploadUtils = new UploadUtils(); + Map result = uploadUtils.uploadFile(request, name); + List imageList = (List) result.get("image"); + String imageUrl = CommonUtils.getImageURL(imageList.get(0)); + return JsonResult.success("上传成功", imageUrl); + } +} diff --git a/common/src/main/java/com/canvas/web/utils/CacheKey.java b/common/src/main/java/com/canvas/web/utils/CacheKey.java new file mode 100644 index 0000000..b3aeaa0 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/CacheKey.java @@ -0,0 +1,39 @@ +package com.canvas.web.utils; + +/** + * 关于缓存的Key集合 + */ +public interface CacheKey { + + /** + * 内置 用户、岗位、应用、菜单、角色 相关key + */ + String USER_MODIFY_TIME_KEY = "user:modify:time:key:"; + String APP_MODIFY_TIME_KEY = "app:modify:time:key:"; + String JOB_MODIFY_TIME_KEY = "job:modify:time:key:"; + String MENU_MODIFY_TIME_KEY = "menu:modify:time:key:"; + String ROLE_MODIFY_TIME_KEY = "role:modify:time:key:"; + String DEPT_MODIFY_TIME_KEY = "dept:modify:time:key:"; + + /** + * 用户 + */ + String USER_ID = "user::id:"; + String USER_NAME = "user::username:"; + /** + * 数据 + */ + String DATE_USER = "data::user:"; + /** + * 菜单 + */ + String MENU_USER = "menu::user:"; + /** + * 角色授权 + */ + String ROLE_AUTH = "role::auth:"; + /** + * 角色信息 + */ + String ROLE_ID = "role::id:"; +} diff --git a/common/src/main/java/com/canvas/web/utils/CallBack.java b/common/src/main/java/com/canvas/web/utils/CallBack.java new file mode 100644 index 0000000..91f66e7 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/CallBack.java @@ -0,0 +1,13 @@ +package com.canvas.web.utils; + +public interface CallBack { + + //回调执行方法 + void executor(); + + + //本回调任务名称 + default String getCallBackName() { + return Thread.currentThread().getId() + ":" + this.getClass().getName(); + } +} diff --git a/common/src/main/java/com/canvas/web/utils/CommonUtils.java b/common/src/main/java/com/canvas/web/utils/CommonUtils.java new file mode 100644 index 0000000..24593e2 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/CommonUtils.java @@ -0,0 +1,294 @@ +package com.canvas.web.utils; + +import com.alibaba.fastjson.JSONObject; +import com.canvas.web.config.CommonConfig; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Field; +import java.security.MessageDigest; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CommonUtils { + /** + * 分转元 + * + * @param amount + * @return + */ + public static String fenToYuan(String amount) { + NumberFormat format = NumberFormat.getInstance(); + try { + Number number = format.parse(amount); + double temp = number.doubleValue() / 100.0; + format.setGroupingUsed(false); + // 设置返回的小数部分所允许的最大位数 + format.setMaximumFractionDigits(2); + amount = format.format(temp); + } catch (ParseException e) { + e.printStackTrace(); + } + return amount; + } + + /** + * 元转分 + * + * @param amount + * @return + */ + public static String yuanToFen(String amount) { + NumberFormat format = NumberFormat.getInstance(); + try { + Number number = format.parse(amount); + double temp = number.doubleValue() * 100.0; + format.setGroupingUsed(false); + // 设置返回数的小数部分所允许的最大位数 + format.setMaximumFractionDigits(0); + amount = format.format(temp); + } catch (ParseException e) { + e.printStackTrace(); + } + return amount; + } + + /** + * 获取当前时间时间戳 + * + * @return + */ + public static Integer timeStamp() { + long currentTime = System.currentTimeMillis(); + String time = String.valueOf(currentTime / 1000); + return Integer.valueOf(time); + } + + /** + * 时间转为日期格式 + * + * @param time + * @param format + * @return + */ + public static String formatTime(Integer time, String format) { + if (StringUtils.isEmpty(time)) { + return ""; + } + SimpleDateFormat dateFormat = new SimpleDateFormat(format); + long timeLong = time.longValue() * 1000; + Date date = new Date(timeLong); + return dateFormat.format(date); + } + + /** + * 获取到图片域名的地址 + * + * @param imageUrl + * @return + */ + public static String getImageURL(String imageUrl) { + return CommonConfig.imageURL + imageUrl; + } + + /** + * 验证邮箱是否正确 + * + * @param email + * @return + */ + public static boolean isEmail(String email) { + boolean flag = false; + try { + String check = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; + Pattern regex = Pattern.compile(check); + Matcher matcher = regex.matcher(email); + flag = matcher.matches(); + } catch (Exception e) { + flag = false; + } + return flag; + } + + /** + * 验证手机号是否正确 + * + * @param mobile + * @return + */ + public static boolean isMobile(String mobile) { + boolean flag = false; + try { + Pattern p = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$"); + Matcher m = p.matcher(mobile); + flag = m.matches(); + } catch (Exception e) { + flag = false; + } + return flag; + } + + /** + * 生成指定位数的随机字符串 + * + * @param isNum 是否是纯数字 + * @param length 长度 + * @return + */ + public static String getRandomStr(boolean isNum, int length) { + String resultStr = ""; + String str = isNum ? "1234567890" : "1234567890abcdefghijkmnpqrstuvwxyz"; + int len = str.length(); + boolean isStop = true; + do { + resultStr = ""; + int count = 0; + for (int i = 0; i < length; i++) { + double dblR = Math.random() * len; + int intR = (int) Math.floor(dblR); + char c = str.charAt(intR); + if (('0' <= c) && (c <= '9')) { + count++; + } + resultStr += str.charAt(intR); + } + if (count >= 2) { + isStop = false; + } + } while (isStop); + return resultStr; + } + + /** + * 判断是否在数组中 + * + * @param s + * @param array + * @return + */ + public static boolean inArray(final String s, final String[] array) { + for (String item : array) { + if (item != null && item.equals(s)) { + return true; + } + } + return false; + } + + /** + * 从html中提取纯文本 + * + * @param strHtml + * @return + */ + public static String stripHtml(String strHtml) { + String content = strHtml.replaceAll("]+>", ""); //剔出的标签 + content = content.replaceAll("\\s*|\t|\r|\n", "");//去除字符串中的空格,回车,换行符,制表符 + return content; + } + + /** + * 去除字符串中的空格、回车、换行符、制表符等 + * + * @param str 原始字符串 + * @return + */ + public static String replaceSpecialStr(String str) { + String repl = ""; + if (str != null) { + Pattern p = Pattern.compile("\\s*|\t|\r|\n"); + Matcher m = p.matcher(str); + repl = m.replaceAll(""); + } + return repl; + } + + /** + * 判断某个元素是否在数组中 + * + * @param key 元素 + * @param map 数组 + * @return + */ + public static boolean inArray(String key, Map map) { + boolean flag = false; + for (String k : map.keySet()) { + if (k.equals(key)) { + flag = true; + } + } + return flag; + } + + /** + * 对象转Map + * + * @param obj 对象 + * @return + * @throws IllegalAccessException + */ + public static Map objectToMap(Object obj) throws IllegalAccessException { + Map map = new HashMap<>(); + Class clazz = obj.getClass(); + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + String fieldName = field.getName(); + Object value = field.get(obj); + map.put(fieldName, value); + } + return map; + } + + /** + * 判断是否是JSON格式 + * + * @param str JSON字符串 + * @return + */ + private boolean isJson(String str) { + try { + JSONObject jsonStr = JSONObject.parseObject(str); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * MD5方法 + * + * @param source + * @return + */ + public static String md5(byte[] source) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(source); + StringBuffer buf = new StringBuffer(); + for (byte b : md.digest()) { + buf.append(String.format("%02x", b & 0xff)); + } + return buf.toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 密码加密 + * + * @param password 密码 + * @return + */ + public static String password(String password) { + String md51 = md5(password.getBytes()); + String pwd = md5((md51 + "IgtUdEQJyVevaCxQnY").getBytes()); + return pwd; + } +} diff --git a/common/src/main/java/com/canvas/web/utils/ElAdminConstant.java b/common/src/main/java/com/canvas/web/utils/ElAdminConstant.java new file mode 100644 index 0000000..e5a6a49 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/ElAdminConstant.java @@ -0,0 +1,26 @@ +package com.canvas.web.utils; + +public class ElAdminConstant { + + /** + * 用于IP定位转换 + */ + public static final String REGION = "内网IP|内网IP"; + /** + * win 系统 + */ + public static final String WIN = "win"; + + /** + * mac 系统 + */ + public static final String MAC = "mac"; + + /** + * 常用接口 + */ + public static class Url { + // IP归属地查询 + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true"; + } +} diff --git a/common/src/main/java/com/canvas/web/utils/ErrorCode.java b/common/src/main/java/com/canvas/web/utils/ErrorCode.java new file mode 100644 index 0000000..0ec7d36 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/ErrorCode.java @@ -0,0 +1,42 @@ +package com.canvas.web.utils; + +public enum ErrorCode { + + FAILED(1, "操作失败"), + TOKEN_MISSING(300, "token丢失"), + TOKEN_ERROR(301, "token认证失败"), + PARAM_MISSING(400, "参数丢失"), + PARAM_ERROR(401, "参数错误"), + SYSTEM_ERROR(500, "系统错误"), + UNKNOWN_ERROR(501, "未知错误"); + + public static final Integer MESSAGE_PARAM_MISSING = 400; + + /** + * 错误码 + */ + private Integer code; + /** + * 错误描述 + */ + private String msg; + + public Integer getCode() { + return this.code; + } + + public String getMsg() { + return this.msg; + } + + /** + * 构造函数 + * + * @param code + * @param msg + */ + private ErrorCode(Integer code, String msg) { + this.code = code; + this.msg = msg; + } +} diff --git a/common/src/main/java/com/canvas/web/utils/FileUtil.java b/common/src/main/java/com/canvas/web/utils/FileUtil.java new file mode 100644 index 0000000..5732b5f --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/FileUtil.java @@ -0,0 +1,356 @@ +package com.canvas.web.utils; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.poi.excel.BigExcelWriter; +import cn.hutool.poi.excel.ExcelUtil; +import com.canvas.web.exception.BaseException; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFCell; +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.security.MessageDigest; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class FileUtil extends cn.hutool.core.io.FileUtil{ + + private static final Logger log = LoggerFactory.getLogger(FileUtil.class); + + /** + * 系统临时目录 + *
+ * windows 包含路径分割符,但Linux 不包含, + * 在windows \\==\ 前提下, + * 为安全起见 同意拼装 路径分割符, + *
+     *       java.io.tmpdir
+     *       windows : C:\Users/xxx\AppData\Local\Temp\
+     *       linux: /temp
+     * 
+ */ + public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator; + /** + * 定义GB的计算常量 + */ + private static final int GB = 1024 * 1024 * 1024; + /** + * 定义MB的计算常量 + */ + private static final int MB = 1024 * 1024; + /** + * 定义KB的计算常量 + */ + private static final int KB = 1024; + + /** + * 格式化小数 + */ + private static final DecimalFormat DF = new DecimalFormat("0.00"); + + public static final String IMAGE = "图片"; + public static final String TXT = "文档"; + public static final String MUSIC = "音乐"; + public static final String VIDEO = "视频"; + public static final String OTHER = "其他"; + + + /** + * MultipartFile转File + */ + public static File toFile(MultipartFile multipartFile) { + // 获取文件名 + String fileName = multipartFile.getOriginalFilename(); + // 获取文件后缀 + String prefix = "." + getExtensionName(fileName); + File file = null; + try { + // 用uuid作为文件名,防止生成的临时文件重复 + file = File.createTempFile(IdUtil.simpleUUID(), prefix); + // MultipartFile to File + multipartFile.transferTo(file); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return file; + } + + /** + * 获取文件扩展名,不带 . + */ + public static String getExtensionName(String filename) { + if ((filename != null) && (filename.length() > 0)) { + int dot = filename.lastIndexOf('.'); + if ((dot > -1) && (dot < (filename.length() - 1))) { + return filename.substring(dot + 1); + } + } + return filename; + } + + /** + * Java文件操作 获取不带扩展名的文件名 + */ + public static String getFileNameNoEx(String filename) { + if ((filename != null) && (filename.length() > 0)) { + int dot = filename.lastIndexOf('.'); + if ((dot > -1) && (dot < (filename.length()))) { + return filename.substring(0, dot); + } + } + return filename; + } + + /** + * 文件大小转换 + */ + public static String getSize(long size) { + String resultSize; + if (size / GB >= 1) { + //如果当前Byte的值大于等于1GB + resultSize = DF.format(size / (float) GB) + "GB "; + } else if (size / MB >= 1) { + //如果当前Byte的值大于等于1MB + resultSize = DF.format(size / (float) MB) + "MB "; + } else if (size / KB >= 1) { + //如果当前Byte的值大于等于1KB + resultSize = DF.format(size / (float) KB) + "KB "; + } else { + resultSize = size + "B "; + } + return resultSize; + } + + /** + * inputStream 转 File + */ + static File inputStreamToFile(InputStream ins, String name) throws Exception { + File file = new File(SYS_TEM_DIR + name); + if (file.exists()) { + return file; + } + OutputStream os = new FileOutputStream(file); + int bytesRead; + int len = 8192; + byte[] buffer = new byte[len]; + while ((bytesRead = ins.read(buffer, 0, len)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.close(); + ins.close(); + return file; + } + + /** + * 将文件名解析成文件的上传路径 + */ + public static File upload(MultipartFile file, String filePath) { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS"); + String name = getFileNameNoEx(file.getOriginalFilename()); + String suffix = getExtensionName(file.getOriginalFilename()); + String nowStr = "-" + format.format(date); + try { + String fileName = name + nowStr + "." + suffix; + String path = filePath + fileName; + // getCanonicalFile 可解析正确各种路径 + File dest = new File(path).getCanonicalFile(); + // 检测是否存在目录 + if (!dest.getParentFile().exists()) { + if (!dest.getParentFile().mkdirs()) { + System.out.println("was not successful."); + } + } + // 文件写入 + file.transferTo(dest); + return dest; + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + /** + * 导出excel + */ + public static void downloadExcel(List> list, HttpServletResponse response) throws IOException { + String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx"; + File file = new File(tempPath); + BigExcelWriter writer = ExcelUtil.getBigWriter(file); + // 一次性写出内容,使用默认样式,强制输出标题 + writer.write(list, true); + SXSSFSheet sheet = (SXSSFSheet)writer.getSheet(); + //上面需要强转SXSSFSheet 不然没有trackAllColumnsForAutoSizing方法 + sheet.trackAllColumnsForAutoSizing(); + //列宽自适应 + writer.autoSizeColumnAll(); + //列宽自适应支持中文单元格 + sizeChineseColumn(sheet, writer); + //response为HttpServletResponse对象 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"); + //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码 + response.setHeader("Content-Disposition", "attachment;filename=file.xlsx"); + ServletOutputStream out = response.getOutputStream(); + // 终止后删除临时文件 + file.deleteOnExit(); + writer.flush(out, true); + //此处记得关闭输出Servlet流 + IoUtil.close(out); + } + + /** + * 自适应宽度(中文支持) + */ + private static void sizeChineseColumn(SXSSFSheet sheet, BigExcelWriter writer) { + for (int columnNum = 0; columnNum < writer.getColumnCount(); columnNum++) { + int columnWidth = sheet.getColumnWidth(columnNum) / 256; + for (int rowNum = 0; rowNum < sheet.getLastRowNum(); rowNum++) { + SXSSFRow currentRow; + if (sheet.getRow(rowNum) == null) { + currentRow = sheet.createRow(rowNum); + } else { + currentRow = sheet.getRow(rowNum); + } + if (currentRow.getCell(columnNum) != null) { + SXSSFCell currentCell = currentRow.getCell(columnNum); + if (currentCell.getCellTypeEnum() == CellType.STRING) { + int length = currentCell.getStringCellValue().getBytes().length; + if (columnWidth < length) { + columnWidth = length; + } + } + } + } + sheet.setColumnWidth(columnNum, columnWidth * 256); + } + } + + public static String getFileType(String type) { + String documents = "txt doc pdf ppt pps xlsx xls docx"; + String music = "mp3 wav wma mpa ram ra aac aif m4a"; + String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg"; + String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg"; + if (image.contains(type)) { + return IMAGE; + } else if (documents.contains(type)) { + return TXT; + } else if (music.contains(type)) { + return MUSIC; + } else if (video.contains(type)) { + return VIDEO; + } else { + return OTHER; + } + } + + public static void checkSize(long maxSize, long size) { + // 1M + int len = 1024 * 1024; + if (size > (maxSize * len)) { + throw new BaseException("文件超出规定大小"); + } + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(File file1, File file2) { + String img1Md5 = getMd5(file1); + String img2Md5 = getMd5(file2); + return img1Md5.equals(img2Md5); + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(String file1Md5, String file2Md5) { + return file1Md5.equals(file2Md5); + } + + private static byte[] getByte(File file) { + // 得到文件长度 + byte[] b = new byte[(int) file.length()]; + try { + InputStream in = new FileInputStream(file); + try { + System.out.println(in.read(b)); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } catch (FileNotFoundException e) { + log.error(e.getMessage(), e); + return null; + } + return b; + } + + private static String getMd5(byte[] bytes) { + // 16进制字符 + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(bytes); + byte[] md = mdTemp.digest(); + int j = md.length; + char[] str = new char[j * 2]; + int k = 0; + // 移位 输出字符串 + for (byte byte0 : md) { + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + /** + * 下载文件 + * + * @param request / + * @param response / + * @param file / + */ + public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) { + response.setCharacterEncoding(request.getCharacterEncoding()); + response.setContentType("application/octet-stream"); + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + response.setHeader("Content-Disposition", "attachment; filename=" + file.getName()); + IOUtils.copy(fis, response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + if (fis != null) { + try { + fis.close(); + if (deleteOnExit) { + file.deleteOnExit(); + } + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + } + } + + public static String getMd5(File file) { + return getMd5(getByte(file)); + } +} diff --git a/common/src/main/java/com/canvas/web/utils/JsonResult.java b/common/src/main/java/com/canvas/web/utils/JsonResult.java new file mode 100644 index 0000000..e70c36a --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/JsonResult.java @@ -0,0 +1,112 @@ +package com.canvas.web.utils; + +import org.springframework.http.HttpStatus; + +import java.io.Serializable; + +public class JsonResult implements Serializable { + + // 错误码 + private Integer code= HttpStatus.OK.value(); + + //提示语 + private String msg= "操作成功"; + + //返回对象 + private Object data; + + public Integer getCode() { + return this.code; + } + + public void setCode(final Integer code) { + this.code = code; + } + + public String getMsg() { + return this.msg; + } + + public void setMsg(final String msg) { + this.msg = msg; + } + + public Object getData() { + return this.data; + } + + public void setData(final Object data) { + this.data = data; + } + + /** + * 构造函数 + */ + public JsonResult() { + } + + public JsonResult(String msg) { + this.msg = msg; + } + + public JsonResult(Object data) { + this.data = data; + } + + public JsonResult(Integer code, String msg) { + this.code = code; + this.msg = msg; + } + + public JsonResult(Integer code, String msg, Object data) { + this.code = code; + this.msg = msg; + this.data = data; + } + + public static JsonResult success() { + return new JsonResult(); + } + + public static JsonResult success(String msg) { + return new JsonResult(msg); + } + + public JsonResult success(Object data) { + return new JsonResult(HttpStatus.OK.value(), msg, data); + } + + public static JsonResult success(String msg, Object data) { + return new JsonResult(HttpStatus.OK.value(), msg, data); + } + + public static JsonResult error() { + return new JsonResult(-1, "操作失败"); + } + + public static JsonResult error(String msg) { + return new JsonResult(-1, msg); + } + + public static JsonResult error(Integer code, String msg) { + return new JsonResult(code, msg); + } + + public static JsonResult error(Integer code, String msg, Object data) { + return new JsonResult(code, msg, data); + } + + public static JsonResult error(ErrorCode errorCode) { + return new JsonResult(errorCode.getCode(), errorCode.getMsg()); + } + + public static JsonResult error(HttpStatus httpStatus, String msg, Object data) { + return new JsonResult(httpStatus.value(), msg, data); + } + + public Object error(HttpStatus httpStatus, String msg) { + this.code = httpStatus.value(); + this.msg = msg; + return this; + } +} diff --git a/common/src/main/java/com/canvas/web/utils/RedisUtils.java b/common/src/main/java/com/canvas/web/utils/RedisUtils.java new file mode 100644 index 0000000..2570217 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/RedisUtils.java @@ -0,0 +1,696 @@ +package com.canvas.web.utils; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisConnectionUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Component +public class RedisUtils { + + private static final Logger log = LoggerFactory.getLogger(RedisUtils.class); + private RedisTemplate redisTemplate; + @Value("${jwt.online-key}") + private String onlineKey; + + public RedisUtils(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @param timeUnit 单位 + */ + public boolean expire(String key, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.expire(key, time, timeUnit); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 根据 key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(Object key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 查找匹配key + * + * @param pattern key + * @return / + */ + public List scan(String pattern) { + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(); + while (cursor.hasNext()) { + result.add(new String(cursor.next())); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 分页查询 key + * + * @param patternKey key + * @param page 页码 + * @param size 每页数目 + * @return / + */ + public List findKeysForPage(String patternKey, int page, int size) { + ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(size); + int tmpIndex = 0; + int fromIndex = page * size; + int toIndex = page * size + size; + while (cursor.hasNext()) { + if (tmpIndex >= fromIndex && tmpIndex < toIndex) { + result.add(new String(cursor.next())); + tmpIndex++; + continue; + } + // 获取到满足条件的数据后,就可以退出了 + if (tmpIndex >= toIndex) { + break; + } + tmpIndex++; + cursor.next(); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除缓存 + * + * @param keys 可以传一个值 或多个 + */ + public void del(String... keys) { + if (keys != null && keys.length > 0) { + if (keys.length == 1) { + boolean result = redisTemplate.delete(keys[0]); + log.debug("--------------------------------------------"); + log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString()); + log.debug("--------------------------------------------"); + } else { + Set keySet = new HashSet<>(); + for (String key : keys) { + keySet.addAll(redisTemplate.keys(key)); + } + long count = redisTemplate.delete(keySet); + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keySet.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); + } + } + } + + // ============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + /** + * 批量获取 + * + * @param keys + * @return + */ + public List multiGet(List keys) { + List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys)); + List resultList = Lists.newArrayList(); + Optional.ofNullable(list).ifPresent(e-> list.forEach(ele-> Optional.ofNullable(ele).ifPresent(resultList::add))); + return resultList; + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public boolean set(String key, Object value) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间 + * @param timeUnit 类型 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, timeUnit); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * HashSet 并设置时间 + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + // ============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) { + expire(key, time); + } + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return / + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + return redisTemplate.opsForList().remove(key, count, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * @param prefix 前缀 + * @param ids id + */ + public void delByKeys(String prefix, Set ids) { + Set keys = new HashSet<>(); + for (Long id : ids) { + keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString())); + } + long count = redisTemplate.delete(keys); + // 此处提示可自行删除 + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keys.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); + } +} diff --git a/common/src/main/java/com/canvas/web/utils/SpringContextHolder.java b/common/src/main/java/com/canvas/web/utils/SpringContextHolder.java new file mode 100644 index 0000000..571ed75 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/SpringContextHolder.java @@ -0,0 +1,121 @@ +package com.canvas.web.utils; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Component +public class SpringContextHolder implements ApplicationContextAware, DisposableBean { + + + private static ApplicationContext applicationContext = null; + private static final List CALL_BACKS = new ArrayList<>(); + private static boolean addCallback = true; + + public synchronized static void addCallBacks(CallBack callBack){ + if (addCallback){ + SpringContextHolder.CALL_BACKS.add(callBack); + }else { + log.warn("CallBack:{} 已无法添加!立即执行",callBack.getCallBackName()); + callBack.executor(); + } + } + + public static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + assertContextInjected(); + return applicationContext.getBean(requiredType); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param defaultValue 默认值 + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, T defaultValue, Class requiredType) { + T result = defaultValue; + try { + result = getBean(Environment.class).getProperty(property, requiredType); + } catch (Exception ignored) {} + return result; + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @return / + */ + public static String getProperties(String property) { + return getProperties(property, null, String.class); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, Class requiredType) { + return getProperties(property, null, requiredType); + } + + /** + * 检查ApplicationContext不为空. + */ + private static void assertContextInjected() { + if (applicationContext == null) { + throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" + + ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); + } + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + private static void clearHolder() { + log.debug("清除SpringContextHolder中的ApplicationContext:" + + applicationContext); + applicationContext = null; + } + + @Override + public void destroy() { + SpringContextHolder.clearHolder(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (SpringContextHolder.applicationContext != null) { + log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext); + } + SpringContextHolder.applicationContext = applicationContext; + if (addCallback) { + for (CallBack callBack : SpringContextHolder.CALL_BACKS) { + callBack.executor(); + } + CALL_BACKS.clear(); + } + SpringContextHolder.addCallback = false; + } +} diff --git a/common/src/main/java/com/canvas/web/utils/StringUtils.java b/common/src/main/java/com/canvas/web/utils/StringUtils.java new file mode 100644 index 0000000..31d3eda --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/StringUtils.java @@ -0,0 +1,268 @@ +package com.canvas.web.utils; + + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import eu.bitwalker.useragentutils.Browser; +import eu.bitwalker.useragentutils.UserAgent; +import org.lionsoul.ip2region.DataBlock; +import org.lionsoul.ip2region.DbConfig; +import org.lionsoul.ip2region.DbSearcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.UnknownHostException; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; + +public class StringUtils extends org.apache.commons.lang3.StringUtils{ + + private static final Logger log = LoggerFactory.getLogger(StringUtils.class); + private static boolean ipLocal = false; + private static File file = null; + private static DbConfig config; + private static final char SEPARATOR = '_'; + private static final String UNKNOWN = "unknown"; + + static { + SpringContextHolder.addCallBacks(() -> { + StringUtils.ipLocal = SpringContextHolder.getProperties("ip.local-parsing", false, Boolean.class); + if (ipLocal) { + /* + * 此文件为独享 ,不必关闭 + */ + String path = "ip2region/ip2region.db"; + String name = "ip2region.db"; + try { + config = new DbConfig(); + file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + }); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + + s = s.toLowerCase(); + + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCapitalizeCamelCase(String s) { + if (s == null) { + return null; + } + s = toCamelCase(s); + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" + * toCapitalizeCamelCase("hello_world") == "HelloWorld" + * toUnderScoreCase("helloWorld") = "hello_world" + */ + static String toUnderScoreCase(String s) { + if (s == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + boolean nextUpperCase = true; + + if (i < (s.length() - 1)) { + nextUpperCase = Character.isUpperCase(s.charAt(i + 1)); + } + + if ((i > 0) && Character.isUpperCase(c)) { + if (!upperCase || !nextUpperCase) { + sb.append(SEPARATOR); + } + upperCase = true; + } else { + upperCase = false; + } + + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 获取ip地址 + */ + public static String getIp(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + String comma = ","; + String localhost = "127.0.0.1"; + if (ip.contains(comma)) { + ip = ip.split(",")[0]; + } + if (localhost.equals(ip)) { + // 获取本机真正的ip地址 + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error(e.getMessage(), e); + } + } + return ip; + } + + /** + * 根据ip获取详细地址 + */ + public static String getCityInfo(String ip) { + if (ipLocal) { + return getLocalCityInfo(ip); + } else { + return getHttpCityInfo(ip); + } + } + + /** + * 根据ip获取详细地址 + */ + public static String getHttpCityInfo(String ip) { + String api = String.format(ElAdminConstant.Url.IP_URL, ip); + JSONObject object = JSONUtil.parseObj(HttpUtil.get(api)); + return object.get("addr", String.class); + } + + /** + * 根据ip获取详细地址 + */ + public static String getLocalCityInfo(String ip) { + try { + DataBlock dataBlock = new DbSearcher(config, file.getPath()) + .binarySearch(ip); + String region = dataBlock.getRegion(); + String address = region.replace("0|", ""); + char symbol = '|'; + if (address.charAt(address.length() - 1) == symbol) { + address = address.substring(0, address.length() - 1); + } + return address.equals(ElAdminConstant.REGION) ? "内网IP" : address; + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return ""; + } + + public static String getBrowser(HttpServletRequest request) { + UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent")); + Browser browser = userAgent.getBrowser(); + return browser.getName(); + } + + /** + * 获得当天是周几 + */ + public static String getWeekDay() { + String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + + int w = cal.get(Calendar.DAY_OF_WEEK) - 1; + if (w < 0) { + w = 0; + } + return weekDays[w]; + } + + /** + * 获取当前机器的IP + * + * @return / + */ + public static String getLocalIp() { + try { + InetAddress candidateAddress = null; + // 遍历所有的网络接口 + for (Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); interfaces.hasMoreElements();) { + NetworkInterface anInterface = interfaces.nextElement(); + // 在所有的接口下再遍历IP + for (Enumeration inetAddresses = anInterface.getInetAddresses(); inetAddresses.hasMoreElements();) { + InetAddress inetAddr = inetAddresses.nextElement(); + // 排除loopback类型地址 + if (!inetAddr.isLoopbackAddress()) { + if (inetAddr.isSiteLocalAddress()) { + // 如果是site-local地址,就是它了 + return inetAddr.getHostAddress(); + } else if (candidateAddress == null) { + // site-local类型的地址未被发现,先记录候选地址 + candidateAddress = inetAddr; + } + } + } + } + if (candidateAddress != null) { + return candidateAddress.getHostAddress(); + } + // 如果没有发现 non-loopback地址.只能用最次选的方案 + InetAddress jdkSuppliedAddress = InetAddress.getLocalHost(); + if (jdkSuppliedAddress == null) { + return ""; + } + return jdkSuppliedAddress.getHostAddress(); + } catch (Exception e) { + return ""; + } + } +} diff --git a/common/src/main/java/com/canvas/web/utils/UploadUtils.java b/common/src/main/java/com/canvas/web/utils/UploadUtils.java new file mode 100644 index 0000000..d8df1b0 --- /dev/null +++ b/common/src/main/java/com/canvas/web/utils/UploadUtils.java @@ -0,0 +1,305 @@ +package com.canvas.web.utils; + +import com.canvas.web.config.UploadFileConfig; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class UploadUtils { + + // 表单字段常量 + public static final String FORM_FIELDS = "form_fields"; + + // 文件域常量 + public static final String FILE_FIELDS = "file"; + // 定义允许上传的文件扩展名 + private Map extMap = new HashMap(); + // 文件保存目录路径 + private String uploadPath = UploadFileConfig.uploadFolder; + // 文件的目录名 + private String dirName = "images"; + // 上传临时路径 + private static final String TEMP_PATH = "temp"; + // 临时存相对路径 + private String tempPath = uploadPath + TEMP_PATH; + // 单个文件最大上传大小(10M) + private long fileMaxSize = 1024 * 1024 * 10; + // 最大文件大小(100M) + private long maxSize = 1024 * 1024 * 100; + // 文件保存目录url + private String saveUrl; + // 文件最终的url包括文件名 + private List fileUrl = new ArrayList<>(); + + /** + * 构造函数 + */ + public UploadUtils() { + // 其中images,flashs,medias,files,对应文件夹名称,对应dirName + // key文件夹名称 + // value该文件夹内可以上传文件的后缀名 + extMap.put("images", "gif,jpg,jpeg,png,bmp"); + extMap.put("flashs", "swf,flv"); + extMap.put("medias", "swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb"); + extMap.put("files", "doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2"); + } + + /** + * 文件上传 + * + * @param request + * @return + */ + @SuppressWarnings("unchecked") + public Map uploadFile(HttpServletRequest request, String name) { + // 验证文件并返回错误信息 + String error = this.validateFields(request, name); + // 初始化表单元素 + Map fieldsMap = new HashMap(); + if (error.equals("")) { + fieldsMap = this.initFields(request); + } + List fiList = (List) fieldsMap.get(UploadUtils.FILE_FIELDS); + if (fiList != null) { + for (FileItem item : fiList) { + // 上传文件并返回错误信息 + error = this.saveFile(item); + } + } + // 返回结果 + Map result = new HashMap<>(); + result.put("error", error); + result.put("image", this.fileUrl); + return result; + } + + /** + * 上传验证并初始化目录 + * + * @param request + * @return + */ + private String validateFields(HttpServletRequest request, String name) { + String errorInfo = ""; + // 获取内容类型 + String contentType = request.getContentType(); + int contentLength = request.getContentLength(); + // 初始化上传路径,不存在则创建 + File uploadDir = new File(uploadPath); + // 目录不存在则创建 + if (!uploadDir.exists()) { + uploadDir.mkdirs(); + } + if (contentType == null || !contentType.startsWith("multipart")) { + // TODO + System.out.println("请求不包含multipart/form-data流"); + errorInfo = "请求不包含multipart/form-data流"; + } else if (maxSize < contentLength) { + // TODO + System.out.println("上传文件大小超出文件最大大小"); + errorInfo = "上传文件大小超出文件最大大小[" + maxSize + "]"; + } else if (!ServletFileUpload.isMultipartContent(request)) { + // TODO + errorInfo = "请选择文件"; + } else if (!uploadDir.isDirectory()) { + // TODO + errorInfo = "上传目录[" + uploadPath + "]不存在"; + } else if (!uploadDir.canWrite()) { + // TODO + errorInfo = "上传目录[" + uploadPath + "]没有写权限"; + } else if (!extMap.containsKey(dirName)) { + // TODO + errorInfo = "目录名不正确"; + } else { + // 上传路径 + uploadPath += dirName + "/" + name + "/"; + // 保存目录Url + saveUrl = dirName + "/" + name + "/"; + + // 创建一级目录 + File saveDirFile = new File(uploadPath); + if (!saveDirFile.exists()) { + saveDirFile.mkdirs(); + } + + // 创建二级目录(格式:年月日) + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String ymd = sdf.format(new Date()); + uploadPath += ymd + "/"; + saveUrl += ymd + "/"; + File dirFile = new File(uploadPath); + if (!dirFile.exists()) { + dirFile.mkdirs(); + } + + // 创建上传临时目录 + File file = new File(tempPath); + if (!file.exists()) { + file.mkdirs(); + } + } + return errorInfo; + } + + /** + * 处理上传内容 + * + * @return + */ +// @SuppressWarnings("unchecked") + private Map initFields(HttpServletRequest request) { + // 存储表单字段和非表单字段 + Map map = new HashMap(); + // 第一步:判断request + boolean isMultipart = ServletFileUpload.isMultipartContent(request); + // 第二步:解析request + if (isMultipart) { + // 设置环境:创建一个DiskFileItemFactory工厂 + DiskFileItemFactory factory = new DiskFileItemFactory(); + // 阀值,超过这个值才会写到临时目录,否则在内存中 + factory.setSizeThreshold(1024 * 1024 * 10); + // 设置上传文件的临时目录 + factory.setRepository(new File(tempPath)); + // 核心操作类:创建一个文件上传解析器。 + ServletFileUpload upload = new ServletFileUpload(factory); + // 设置文件名称编码(解决上传"文件名"的中文乱码) + upload.setHeaderEncoding("UTF-8"); + // 限制单个文件上传大小 + upload.setFileSizeMax(fileMaxSize); + // 限制总上传文件大小 + upload.setSizeMax(maxSize); + // 使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List集合,每一个FileItem对应一个Form表单的输入项 + List items = null; + try { + items = upload.parseRequest(request); + } catch (FileUploadException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // 第3步:处理uploaded items + if (items != null && items.size() > 0) { + Iterator iter = items.iterator(); + // 文件域对象 + List list = new ArrayList(); + // 表单字段 + Map fields = new HashMap(); + while (iter.hasNext()) { + FileItem item = iter.next(); + // 处理所有表单元素和文件域表单元素 + if (item.isFormField()) { + // 如果fileitem中封装的是普通输入项的数据(输出名、值) + String name = item.getFieldName();// 普通输入项数据的名 + String value = item.getString(); + fields.put(name, value); + } else { + //如果fileitem中封装的是上传文件,得到上传的文件名称 + // 文件域表单元素 + list.add(item); + } + } + map.put(FORM_FIELDS, fields); + map.put(FILE_FIELDS, list); + } + } + return map; + } + + /** + * 保存文件 + * + * @param item + * @return + */ + private String saveFile(FileItem item) { + String error = ""; + String fileName = item.getName(); + String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + + if (item.getSize() > maxSize) { // 检查文件大小 + // TODO + error = "上传文件大小超过限制"; + } else if (!Arrays.asList(extMap.get(dirName).split(",")).contains(fileExt)) {// 检查扩展名 + error = "上传文件扩展名是不允许的扩展名。\n只允许" + extMap.get(dirName) + "格式。"; + } else { + // 存储文件重命名 + SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); + String newFileName = df.format(new Date()) + new Random().nextInt(1000) + "." + fileExt; + + // 新增值文件数组 + String filePath = saveUrl + newFileName; + fileUrl.add(filePath); + + // 写入文件 + try { + File uploadedFile = new File(uploadPath, newFileName); + item.write(uploadedFile); + } catch (IOException e) { + e.printStackTrace(); + System.out.println("上传失败了!!!"); + } catch (Exception e) { + e.printStackTrace(); + } + } + return error; + } + + /** + * *********************get/set方法********************************* + */ + public String getSaveUrl() { + return saveUrl; + } + + public String getUploadPath() { + return uploadPath; + } + + public long getMaxSize() { + return maxSize; + } + + public void setMaxSize(long maxSize) { + this.maxSize = maxSize; + } + + public Map getExtMap() { + return extMap; + } + + public void setExtMap(Map extMap) { + this.extMap = extMap; + } + + public String getDirName() { + return dirName; + } + + public void setDirName(String dirName) { + this.dirName = dirName; + } + + public String getTempPath() { + return tempPath; + } + + public void setTempPath(String tempPath) { + this.tempPath = tempPath; + } + + public List getFileUrl() { + return fileUrl; + } + + public void setFileUrl(List fileUrl) { + this.fileUrl = fileUrl; + } + +} diff --git a/pom.xml b/pom.xml index bed6888..aa084e4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,18 +4,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.6.2 - - + com.canvas.web yxk_canvasScreen pom 1.0-SNAPSHOT - admin common generator quartz @@ -25,62 +19,233 @@ 多媒体后台管理系统 + + org.springframework.boot + spring-boot-starter-parent + 2.6.1 + - 17 - 17 - - true - - 1.0-SNAPSHOT + UTF-8 + UTF-8 + 17 + 1.16 + 2.9.2 + 1.2.79 + 1.2.8 + 2.11.1 + 1.4.2.Final - - - - - com.javaweb - javaweb-common - ${version} - - - com.javaweb - javaweb-generator - ${version} - - - com.javaweb - javaweb-system - ${version} - - - com.javaweb - javaweb-admin - ${version} - - - com.javaweb - javaweb-quartz - ${version} - - - com.javaweb - javaweb-queue - ${version} - - - - - org.projectlombok - lombok - 1.18.10 - true - - - com.alibaba - fastjson - 1.2.62 - - - + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-cache + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + + org.bgee.log4jdbc-log4j2 + log4jdbc-log4j2-jdbc4.1 + ${log4jdbc.version} + + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + io.swagger + swagger-annotations + + + io.swagger + swagger-models + + + + + io.springfox + springfox-swagger-ui + ${swagger.version} + + + io.swagger + swagger-annotations + 1.5.21 + + + io.swagger + swagger-models + 1.5.21 + + + + com.github.xiaoymin + swagger-bootstrap-ui + 1.9.6 + + + + + mysql + mysql-connector-java + 8.0.27 + runtime + + + + org.postgresql + postgresql + 42.2.19 + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + org.lionsoul + ip2region + 1.7.2 + + + + + org.projectlombok + lombok + 1.18.22 + true + + + + + org.apache.poi + poi + 3.17 + + + org.apache.poi + poi-ooxml + 3.17 + + + xerces + xercesImpl + 2.12.0 + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + provided + + + javax.inject + javax.inject + 1 + + + + + com.github.whvcse + easy-captcha + 1.6.2 + + + + org.openjdk.nashorn + nashorn-core + 15.3 + + + + + eu.bitwalker + UserAgentUtils + 1.21 + + + + javax.persistence + javax.persistence-api + 2.2 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + \ No newline at end of file diff --git a/system/pom.xml b/system/pom.xml index 14a3d2e..9409ef6 100644 --- a/system/pom.xml +++ b/system/pom.xml @@ -13,40 +13,60 @@ 系统模块 - 17 - 17 + 0.11.2 + + 5.6.0 - - com.canvas.web common + 1.0-SNAPSHOT + - org.projectlombok - lombok - true + io.jsonwebtoken + jjwt-api + ${jjwt.version} - - org.apache.shiro - shiro-spring - 1.7.1 + io.jsonwebtoken + jjwt-impl + ${jjwt.version} - - org.crazycake - shiro-redis - 3.3.1 + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} - + + + + ch.ethz.ganymed + ganymed-ssh2 + build210 + + + com.jcraft + jsch + 0.1.55 + + + - com.alibaba - druid-spring-boot-starter - 1.2.8 + com.github.oshi + oshi-core + 5.3.6 + + + + + org.quartz-scheduler + quartz + + \ No newline at end of file diff --git a/system/src/main/java/AppRun.java b/system/src/main/java/AppRun.java new file mode 100644 index 0000000..ff08566 --- /dev/null +++ b/system/src/main/java/AppRun.java @@ -0,0 +1,45 @@ +import com.canvas.web.annotation.rest.AnonymousGetMapping; +import com.canvas.web.utils.SpringContextHolder; +import io.swagger.annotations.Api; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.bind.annotation.RestController; + +@EnableAsync +@RestController +@Api(hidden = true) +@SpringBootApplication +@EnableTransactionManagement +@EnableJpaAuditing(auditorAwareRef = "auditorAware") +public class AppRun { + + public static void main(String[] args) { + SpringApplication.run(AppRun.class, args); + } + + @Bean + public SpringContextHolder springContextHolder(){ + return new SpringContextHolder(); + } + + public ServletWebServerFactory webServerFactory(){ + TomcatServletWebServerFactory fa=new TomcatServletWebServerFactory(); + fa.addConnectorCustomizers(connector -> connector.setProperty("relaxedQueryChars","[]{}")); + return fa; + } + + /** + * 访问首页提示 + * @return + */ + @AnonymousGetMapping("/") + public String index(){ + return "Backend service started successfully"; + } +} diff --git a/system/src/main/java/modules/security/config/ConfigBeanConfiguration.java b/system/src/main/java/modules/security/config/ConfigBeanConfiguration.java new file mode 100644 index 0000000..4d570bf --- /dev/null +++ b/system/src/main/java/modules/security/config/ConfigBeanConfiguration.java @@ -0,0 +1,24 @@ +package modules.security.config; + + +import modules.security.config.bean.LoginProperties; +import modules.security.config.bean.SecurityProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ConfigBeanConfiguration { + + @Bean + @ConfigurationProperties(prefix="login",ignoreUnknownFields=true) + public LoginProperties loginProperties(){ + return new LoginProperties(); + } + + @Bean + @ConfigurationProperties(prefix = "jwt",ignoreUnknownFields = true) + public SecurityProperties securityProperties(){ + return new SecurityProperties(); + } +} diff --git a/system/src/main/java/modules/security/config/SpringSecurityConfig.java b/system/src/main/java/modules/security/config/SpringSecurityConfig.java new file mode 100644 index 0000000..b86cfd7 --- /dev/null +++ b/system/src/main/java/modules/security/config/SpringSecurityConfig.java @@ -0,0 +1,31 @@ +package modules.security.config; + +import lombok.RequiredArgsConstructor; +import modules.security.security.JwtAccessDeniedHandler; +import modules.security.security.JwtAuthenticationEntryPoint; +import modules.security.security.SecurityProperties; +import modules.security.security.TokenProvider; +import modules.security.service.UserCacheClean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.web.filter.CorsFilter; + + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true) +public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { + + private final TokenProvider tokenProvider; + + private final CorsFilter corsFilter; + private final JwtAuthenticationEntryPoint authenticationErrorHandler; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + private final ApplicationContext applicationContext; + private final SecurityProperties properties; + private final UserCacheClean userCacheClean; +} diff --git a/system/src/main/java/modules/security/config/bean/LoginCode.java b/system/src/main/java/modules/security/config/bean/LoginCode.java new file mode 100644 index 0000000..1b4e08d --- /dev/null +++ b/system/src/main/java/modules/security/config/bean/LoginCode.java @@ -0,0 +1,48 @@ +package modules.security.config.bean; + +import lombok.Data; + +@Data +public class LoginCode { + + /** + * 验证码配置 + */ + private LoginCodeEnum codeType; + + + /** + * 验证码有效期 分钟 + */ + private Long expiration = 2L; + + /** + * 验证码内容长度 + */ + private int length = 2; + + /** + * 验证码宽度 + */ + private int width = 111; + + /** + * 验证码高度 + */ + private int height = 36; + + /** + * 验证码字体 + */ + private String fontName; + + /** + * 字体大小 + */ + private int fontSize = 25; + + + public LoginCodeEnum getCodeType() { + return codeType; + } +} diff --git a/system/src/main/java/modules/security/config/bean/LoginCodeEnum.java b/system/src/main/java/modules/security/config/bean/LoginCodeEnum.java new file mode 100644 index 0000000..d99bfbb --- /dev/null +++ b/system/src/main/java/modules/security/config/bean/LoginCodeEnum.java @@ -0,0 +1,25 @@ +package modules.security.config.bean; + +//验证码配置枚举 +public enum LoginCodeEnum { + /** + * 算术 + */ + arithmetic, + + /** + * 中文 + */ + chinese, + + /** + * 中文闪图 + */ + chinese_gif, + + /** + * 闪图 + */ + gif, + spec +} diff --git a/system/src/main/java/modules/security/config/bean/LoginProperties.java b/system/src/main/java/modules/security/config/bean/LoginProperties.java new file mode 100644 index 0000000..44b4eec --- /dev/null +++ b/system/src/main/java/modules/security/config/bean/LoginProperties.java @@ -0,0 +1,91 @@ +package modules.security.config.bean; + +import com.canvas.web.exception.BaseException; +import com.canvas.web.exception.user.UserException; +import com.canvas.web.utils.StringUtils; +import com.wf.captcha.*; +import com.wf.captcha.base.Captcha; +import lombok.Data; + +import java.awt.*; +import java.util.Objects; + +@Data +public class LoginProperties { + + /** + * 账号单用户 登录 + */ + private boolean singleLogin = false; + + + private LoginCode loginCode; + + + /** + * 用户登录信息缓存 + */ + private boolean cacheEnable; + + + public boolean isSingleLogin() { + return singleLogin; + } + + public boolean isCacheEnable() { + return cacheEnable; + } + + /** + * 获取验证码生产类 + * @return + */ + public Captcha getCaptcha(){ + if (Objects.isNull(loginCode)){ + loginCode=new LoginCode(); + if (Objects.isNull(loginCode.getCodeType())){ + loginCode.setCodeType(LoginCodeEnum.arithmetic); + } + } + return switchCaptcha(loginCode); + } + + /** + * 依据配置信息生产验证码 + * @param loginCode + * @return + */ + private Captcha switchCaptcha(LoginCode loginCode){ + Captcha captcha; + synchronized (this){ + switch (loginCode.getCodeType()){ + case arithmetic: + captcha=new ArithmeticCaptcha(loginCode.getWidth(),loginCode.getHeight()); + + //几位数运算,默认是两位 + captcha.setLen(loginCode.getLength()); + break; + case chinese: + captcha=new ChineseCaptcha(loginCode.getWidth(),loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + case chinese_gif: + captcha=new ChineseGifCaptcha(loginCode.getWidth(),loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + break; + case gif: + captcha=new GifCaptcha(loginCode.getWidth(),loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + case spec: + captcha=new SpecCaptcha(loginCode.getWidth(),loginCode.getHeight()); + captcha.setLen(loginCode.getLength()); + break; + default: + throw new BaseException("验证码配置信息错误!正确配置查看 LoginCodeEnum"); + } + } + if (StringUtils.isNotBlank(loginCode.getFontName())){ + captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize())); + } + return captcha; + } +} diff --git a/system/src/main/java/modules/security/config/bean/SecurityProperties.java b/system/src/main/java/modules/security/config/bean/SecurityProperties.java new file mode 100644 index 0000000..e0a2992 --- /dev/null +++ b/system/src/main/java/modules/security/config/bean/SecurityProperties.java @@ -0,0 +1,52 @@ +package modules.security.config.bean; + + +import lombok.Data; + +@Data +public class SecurityProperties { + + /** + * Request Headers:Authorization + */ + private String header; + + /** + * 令牌前缀,最后留个空格 Bearer + */ + private String tokenStartWith; + + /** + * 必须使用最少88位的Base64对该令牌进行编码 + */ + private String base64Secret; + + /** + * 令牌过期时间此处单位/毫秒 + */ + private Long tokenValidityInSeconds; + + /** + * 在线用户 key,根据 key 查询 redis 中在线用户的数据 + */ + private String onlineKey; + + /** + * 验证码key + */ + private String codeKey; + + /** + * token 续期检查 + */ + private Long detect; + + /** + * 续期时间 + */ + private Long renew; + + public String getTokenStartWith(){ + return tokenStartWith+" "; + } +} diff --git a/system/src/main/java/modules/security/security/JwtAccessDeniedHandler.java b/system/src/main/java/modules/security/security/JwtAccessDeniedHandler.java new file mode 100644 index 0000000..6d31a15 --- /dev/null +++ b/system/src/main/java/modules/security/security/JwtAccessDeniedHandler.java @@ -0,0 +1,19 @@ +package modules.security.security; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + //当前用户在没有权限的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应 + response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage()); + } +} diff --git a/system/src/main/java/modules/security/security/JwtAuthenticationEntryPoint.java b/system/src/main/java/modules/security/security/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..eeff1fa --- /dev/null +++ b/system/src/main/java/modules/security/security/JwtAuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +package modules.security.security; + + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException{ + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage()); + } +} diff --git a/system/src/main/java/modules/security/security/SecurityProperties.java b/system/src/main/java/modules/security/security/SecurityProperties.java new file mode 100644 index 0000000..ed8cfe9 --- /dev/null +++ b/system/src/main/java/modules/security/security/SecurityProperties.java @@ -0,0 +1,51 @@ +package modules.security.security; + + +import lombok.Data; + +@Data +public class SecurityProperties { + /** + * Request Headers:Authorization + */ + private String header; + + /** + * 令牌前缀,最后留个空格 Bearer + */ + private String tokenStartWith; + + /** + * 必须使用最少88位的Base64对该令牌进行编码 + */ + private String base64Secret; + + /** + * 令牌过期时间此处单位/毫秒 + */ + private Long tokenValidityInSeconds; + + /** + * 在线用户 key,根据 key 查询 redis 中在线用户的数据 + */ + private String onlineKey; + + /** + * 验证码key + */ + private String codeKey; + + /** + * token 续期检查 + */ + private Long detect; + + /** + * 续期时间 + */ + private Long renew; + + public String getTokenStartWith(){ + return tokenStartWith+" "; + } +} diff --git a/system/src/main/java/modules/security/security/TokenConfigurer.java b/system/src/main/java/modules/security/security/TokenConfigurer.java new file mode 100644 index 0000000..31203a1 --- /dev/null +++ b/system/src/main/java/modules/security/security/TokenConfigurer.java @@ -0,0 +1,16 @@ +package modules.security.security; + +import lombok.RequiredArgsConstructor; +import modules.security.service.OnlineUserService; +import modules.security.service.UserCacheClean; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; + +@RequiredArgsConstructor +public class TokenConfigurer extends SecurityConfigurerAdapter { + private final TokenProvider tokenProvider; + private final SecurityProperties properties; + private final OnlineUserService onlineUserService; + private final UserCacheClean userCacheClean; +} diff --git a/system/src/main/java/modules/security/security/TokenProvider.java b/system/src/main/java/modules/security/security/TokenProvider.java new file mode 100644 index 0000000..4d33f8e --- /dev/null +++ b/system/src/main/java/modules/security/security/TokenProvider.java @@ -0,0 +1,107 @@ +package modules.security.security; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import com.canvas.web.utils.RedisUtils; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.security.Key; +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class TokenProvider implements InitializingBean { + + private final SecurityProperties properties; + + private final RedisUtils redisUtils; + public static final String AUTHORITIES_KEY="auth"; + + private JwtParser jwtParser; + + private JwtBuilder jwtBuilder; + + public TokenProvider(SecurityProperties properties,RedisUtils redisUtils){ + this.properties=properties; + this.redisUtils=redisUtils; + } + + @Override + public void afterPropertiesSet() { + byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret()); + Key key = Keys.hmacShaKeyFor(keyBytes); + jwtParser = Jwts.parserBuilder() + .setSigningKey(key) + .build(); + jwtBuilder = Jwts.builder() + .signWith(key, SignatureAlgorithm.HS512); + } + + /** + * 创建Token 设置永不过期, + * Token 的时间有效性转到Redis 维护 + * @param authentication + * @return + */ + public String createToken(Authentication authentication) { + return jwtBuilder + // 加入ID确保生成的 Token 都不一致 + .setId(IdUtil.simpleUUID()) + .setSubject(authentication.getName()) + .compact(); + } + + /** + * 依据Token 获取鉴权信息 + * @param token + * @return + */ + Authentication getAuthentication(String token){ + Claims claims=getClaims(token); + User principal = new User(claims.getSubject(), "******", new ArrayList<>()); + return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>()); + } + + public Claims getClaims(String token) { + return jwtParser + .parseClaimsJws(token) + .getBody(); + } + + /** + * @param token 需要检查的token + */ + public void checkRenewal(String token) { + // 判断是否续期token,计算token的过期时间 + long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000; + Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time); + // 判断当前时间与过期时间的时间差 + long differ = expireDate.getTime() - System.currentTimeMillis(); + // 如果在续期检查的范围内,则续期 + if (differ <= properties.getDetect()) { + long renew = time + properties.getRenew(); + redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS); + } + } + + public String getToken(HttpServletRequest request) { + final String requestHeader = request.getHeader(properties.getHeader()); + if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) { + return requestHeader.substring(7); + } + return null; + } +} diff --git a/system/src/main/java/modules/security/service/OnlineUserService.java b/system/src/main/java/modules/security/service/OnlineUserService.java new file mode 100644 index 0000000..5a97116 --- /dev/null +++ b/system/src/main/java/modules/security/service/OnlineUserService.java @@ -0,0 +1,19 @@ +package modules.security.service; + +import com.canvas.web.utils.RedisUtils; +import lombok.extern.slf4j.Slf4j; +import modules.security.config.bean.SecurityProperties; +import org.springframework.stereotype.Service; + + +@Service +@Slf4j +public class OnlineUserService { + private final SecurityProperties properties; + private final RedisUtils redisUtils; + + public OnlineUserService(SecurityProperties properties, RedisUtils redisUtils) { + this.properties = properties; + this.redisUtils = redisUtils; + } +} diff --git a/system/src/main/java/modules/security/service/UserCacheClean.java b/system/src/main/java/modules/security/service/UserCacheClean.java new file mode 100644 index 0000000..5d8c3df --- /dev/null +++ b/system/src/main/java/modules/security/service/UserCacheClean.java @@ -0,0 +1,31 @@ +package modules.security.service; + + + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +//用于清理用户登录信息缓存 +@Component +public class UserCacheClean { + + /** + * 清理特定用户缓存信息 + * 用户信息变更时 + * + * @param userName + */ + public void cleanUserCache(String userName) { + if (StringUtils.isNotEmpty(userName)) { + UserDetailsServiceImpl.userDtoCache.remove(userName); + } + } + + /** + * 清理所有用户的缓存信息 + * ,如发生角色授权信息变化,可以简便的全部失效缓存 + */ + public void cleanAll() { + UserDetailsServiceImpl.userDtoCache.clear(); + } +} diff --git a/system/src/main/java/modules/security/service/UserDetailsServiceImpl.java b/system/src/main/java/modules/security/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..b432b3a --- /dev/null +++ b/system/src/main/java/modules/security/service/UserDetailsServiceImpl.java @@ -0,0 +1,74 @@ +package modules.security.service; + +import com.canvas.web.exception.BaseException; +import lombok.RequiredArgsConstructor; +import modules.security.config.bean.LoginProperties; +import modules.security.service.dto.JwtUserDto; +import modules.system.service.DataService; +import modules.system.service.RoleService; +import modules.system.service.UserService; + +import modules.system.service.dto.UserDto; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + + +import javax.persistence.EntityNotFoundException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +@RequiredArgsConstructor +@Service("userDetailsService") +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserService userService; + private final RoleService roleService; + private final DataService dataService; + private final LoginProperties loginProperties; + + public void setEnableCache(boolean enableCache) { + this.loginProperties.setCacheEnable(enableCache); + } + + /** + * 用户信息缓存 + * + * + */ + static Map userDtoCache = new ConcurrentHashMap<>(); + + + @Override + public JwtUserDto loadUserByUsername(String username) { + boolean searchDb = true; + JwtUserDto jwtUserDto = null; + if (loginProperties.isCacheEnable() && userDtoCache.containsKey(username)) { + jwtUserDto = userDtoCache.get(username); + searchDb = false; + } + if (searchDb) { + UserDto user; + try { + user = userService.findByName(username); + } catch (EntityNotFoundException e) { + // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException + throw new UsernameNotFoundException("", e); + } + if (user == null) { + throw new UsernameNotFoundException(""); + } else { + if (!user.getEnabled()) { + throw new BaseException("账号未激活!"); + } + jwtUserDto = new JwtUserDto( + user, + dataService.getDeptIds(user), + roleService.mapToGrantedAuthorities(user) + ); + userDtoCache.put(username, jwtUserDto); + } + } + return jwtUserDto; + } +} diff --git a/system/src/main/java/modules/security/service/dto/AuthUserDto.java b/system/src/main/java/modules/security/service/dto/AuthUserDto.java new file mode 100644 index 0000000..d8d1b8d --- /dev/null +++ b/system/src/main/java/modules/security/service/dto/AuthUserDto.java @@ -0,0 +1,22 @@ +package modules.security.service.dto; + + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +@Getter +@Setter +public class AuthUserDto { + + @NotBlank + private String username; + + @NotBlank + private String password; + + private String code; + + private String uuid = ""; +} diff --git a/system/src/main/java/modules/security/service/dto/JwtUserDto.java b/system/src/main/java/modules/security/service/dto/JwtUserDto.java new file mode 100644 index 0000000..8fc0ec6 --- /dev/null +++ b/system/src/main/java/modules/security/service/dto/JwtUserDto.java @@ -0,0 +1,65 @@ +package modules.security.service.dto; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AllArgsConstructor; +import lombok.Getter; +import modules.system.service.dto.UserDto; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Getter +@AllArgsConstructor +public class JwtUserDto implements UserDetails { + + private final UserDto user; + + private final List dataScopes; + + @JsonIgnore + private final List authorities; + + public Set getRoles() { + return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); + } + + @Override + @JsonIgnore + public String getPassword() { + return user.getPassword(); + } + + @Override + @JsonIgnore + public String getUsername() { + return user.getUsername(); + } + + @JsonIgnore + @Override + public boolean isAccountNonExpired() { + return true; + } + + @JsonIgnore + @Override + public boolean isAccountNonLocked() { + return true; + } + + @JsonIgnore + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + @JsonIgnore + public boolean isEnabled() { + return user.getEnabled(); + } +} diff --git a/system/src/main/java/modules/system/service/DataService.java b/system/src/main/java/modules/system/service/DataService.java new file mode 100644 index 0000000..94dd297 --- /dev/null +++ b/system/src/main/java/modules/system/service/DataService.java @@ -0,0 +1,15 @@ +package modules.system.service; + +import modules.system.service.dto.UserDto; + +import java.util.List; + +public interface DataService { + + /** + * 获取数据权限 + * @param user / + * @return / + */ + List getDeptIds(UserDto user); +} diff --git a/system/src/main/java/modules/system/service/RoleService.java b/system/src/main/java/modules/system/service/RoleService.java new file mode 100644 index 0000000..04c5327 --- /dev/null +++ b/system/src/main/java/modules/system/service/RoleService.java @@ -0,0 +1,16 @@ +package modules.system.service; + +import modules.system.service.dto.UserDto; +import org.springframework.security.core.GrantedAuthority; + +import java.util.List; + +public interface RoleService { + + /** + * 获取用户权限信息 + * @param user 用户信息 + * @return 权限信息 + */ + List mapToGrantedAuthorities(UserDto user); +} diff --git a/system/src/main/java/modules/system/service/UserService.java b/system/src/main/java/modules/system/service/UserService.java new file mode 100644 index 0000000..b1dda96 --- /dev/null +++ b/system/src/main/java/modules/system/service/UserService.java @@ -0,0 +1,98 @@ +package modules.system.service; + +import modules.system.service.dto.UserDto; +import modules.system.service.dto.UserQueryCriteria; +import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface UserService { + + /** + * 根据ID查询 + * @param id ID + * @return / + */ + UserDto findById(long id); + + /** + * 新增用户 + * @param resources / + */ + // void create(User resources); + + /** + * 编辑用户 + * @param resources / + */ + // void update(User resources); + + /** + * 删除用户 + * @param ids / + */ + void delete(Set ids); + + /** + * 根据用户名查询 + * @param userName / + * @return / + */ + UserDto findByName(String userName); + + + /** + * 修改密码 + * @param username 用户名 + * @param encryptPassword 密码 + */ + void updatePass(String username, String encryptPassword); + + /** + * 修改头像 + * @param file 文件 + * @return / + */ + Map updateAvatar(MultipartFile file); + + /** + * 修改邮箱 + * @param username 用户名 + * @param email 邮箱 + */ + void updateEmail(String username, String email); + + /** + * 查询全部 + * @param criteria 条件 + * @param pageable 分页参数 + * @return / + */ + Object queryAll(UserQueryCriteria criteria, Pageable pageable); + + /** + * 查询全部不分页 + * @param criteria 条件 + * @return / + */ + List queryAll(UserQueryCriteria criteria); + + /** + * 导出数据 + * @param queryAll 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List queryAll, HttpServletResponse response) throws IOException; + + /** + * 用户自助修改资料 + * @param resources / + */ + // void updateCenter(User resources); +} diff --git a/system/src/main/java/modules/system/service/dto/MenuDto.java b/system/src/main/java/modules/system/service/dto/MenuDto.java new file mode 100644 index 0000000..48866ec --- /dev/null +++ b/system/src/main/java/modules/system/service/dto/MenuDto.java @@ -0,0 +1,76 @@ +package modules.system.service.dto; + +import com.canvas.web.base.BaseDTO; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + + +@Getter +@Setter +public class MenuDto extends BaseDTO implements Serializable { + + private Long id; + + private List children; + + private Integer type; + + private String permission; + + private String title; + + private Integer menuSort; + + private String path; + + private String component; + + private Long pid; + + private Integer subCount; + + private Boolean iFrame; + + private Boolean cache; + + private Boolean hidden; + + private String componentName; + + private String icon; + + private Integer showPosition; + + public Boolean getHasChildren() { + return subCount > 0; + } + + public Boolean getLeaf() { + return subCount <= 0; + } + + public String getLabel() { + return title; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MenuDto menuDto = (MenuDto) o; + return Objects.equals(id, menuDto.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/system/src/main/java/modules/system/service/dto/RoleDto.java b/system/src/main/java/modules/system/service/dto/RoleDto.java new file mode 100644 index 0000000..d765a9d --- /dev/null +++ b/system/src/main/java/modules/system/service/dto/RoleDto.java @@ -0,0 +1,45 @@ +package modules.system.service.dto; + +import com.canvas.web.base.BaseDTO; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +public class RoleDto extends BaseDTO implements Serializable { + + private Long id; + + private Set menus; + + // private Set depts; + + private String name; + + private String dataScope; + + private Integer level; + + private String description; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RoleDto roleDto = (RoleDto) o; + return Objects.equals(id, roleDto.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/system/src/main/java/modules/system/service/dto/RoleSmallDto.java b/system/src/main/java/modules/system/service/dto/RoleSmallDto.java new file mode 100644 index 0000000..03e1bb7 --- /dev/null +++ b/system/src/main/java/modules/system/service/dto/RoleSmallDto.java @@ -0,0 +1,16 @@ +package modules.system.service.dto; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class RoleSmallDto implements Serializable { + private Long id; + + private String name; + + private Integer level; + + private String dataScope; +} diff --git a/system/src/main/java/modules/system/service/dto/UserDto.java b/system/src/main/java/modules/system/service/dto/UserDto.java new file mode 100644 index 0000000..67fc802 --- /dev/null +++ b/system/src/main/java/modules/system/service/dto/UserDto.java @@ -0,0 +1,50 @@ +package modules.system.service.dto; + + +import com.canvas.web.base.BaseDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Date; +import java.util.Set; + +@Getter +@Setter +public class UserDto extends BaseDTO implements Serializable { + + private Long id; + + private Set roles; + + //private Set jobs; + + // private DeptSmallDto dept; + + private Long deptId; + + private String username; + + private String nickName; + + private String email; + + private String phone; + + private String gender; + + private String avatarName; + + private String avatarPath; + + @JsonIgnore + private String password; + + private Boolean enabled; + + @JsonIgnore + private Boolean isAdmin = false; + + private Date pwdResetTime; +} diff --git a/system/src/main/java/modules/system/service/dto/UserQueryCriteria.java b/system/src/main/java/modules/system/service/dto/UserQueryCriteria.java new file mode 100644 index 0000000..7c56191 --- /dev/null +++ b/system/src/main/java/modules/system/service/dto/UserQueryCriteria.java @@ -0,0 +1,32 @@ +package modules.system.service.dto; + +import com.canvas.web.annotation.Query; +import lombok.Data; + + +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Data +public class UserQueryCriteria implements Serializable { + + @Query + private Long id; + + @Query(propName = "id", type = Query.Type.IN, joinName = "dept") + private Set deptIds = new HashSet<>(); + + @Query(blurry = "email,username,nickName") + private String blurry; + + @Query + private Boolean enabled; + + private Long deptId; + + @Query(type = Query.Type.BETWEEN) + private List createTime; +} diff --git a/system/src/main/resources/config/application-dev.yml b/system/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..01767cc --- /dev/null +++ b/system/src/main/resources/config/application-dev.yml @@ -0,0 +1,115 @@ +spring: + datasource: + druid: + db-type: com.alibaba.druid.pool.DruidDataSource + driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy + url: jdbc:log4jdbc:mysql://${DB_HOST:192.168.99.207}:${DB_PORT:3306}/${DB_NAME:yxkadmin}?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&useSSL=false + username: ${DB_USER:root} + password: ${DB_PWD:ftzn83560792} + # 初始连接数 + initial-size: 5 + # 最小连接数 + min-idle: 10 + # 最大连接数 + max-active: 20 + # 获取连接超时时间 + max-wait: 5000 + # 连接有效性检测时间 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存的时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存的时间 + max-evictable-idle-time-millis: 900000 + test-while-idle: true + test-on-borrow: false + test-on-return: false + useGlobalDataSourceStat: true + # 检测连接是否有效 + validation-query: select 1 + # 配置监控统计 + webStatFilter: + enabled: true + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + login-username: admin + login-password: 123456 + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + +# 登录相关配置 +login: + # 登录缓存 + cache-enable: true + # 是否限制单用户登录 + single-login: false + # 验证码 + login-code: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + heigth: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体 + font-name: + # 字体大小 + font-size: 25 + +#jwt +jwt: + header: Authorization + # 令牌前缀 + token-start-with: Bearer + # 必须使用最少88位的Base64对该令牌进行编码 + base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= + # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html + token-validity-in-seconds: 14400000 + # 在线用户key + online-key: online-token- + # 验证码 + code-key: code-key- + # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 + detect: 1800000 + # 续期时间范围,默认1小时,单位毫秒 + renew: 3600000 + +#是否允许生成代码,生产环境设置为false +generator: + enabled: true + +#是否开启 swagger-ui +swagger: + enabled: true + +# IP 本地解析 +ip: + local-parsing: true + +# 文件存储路径 +file: + mac: + path: ~/file/ + avatar: ~/avatar/ + linux: + path: /home/canvasscreen/file/ + avatar: /home/canvasscreen/avatar/ + windows: + path: F:\code\canvasscreen\file\ + avatar: F:\code\canvasscreen\avatar\ + # 文件大小 /M + maxSize: 100 + avatarMaxSize: 5 \ No newline at end of file diff --git a/admin/src/main/resources/application-prod.yml b/system/src/main/resources/config/application-prod.yml similarity index 100% rename from admin/src/main/resources/application-prod.yml rename to system/src/main/resources/config/application-prod.yml diff --git a/system/src/main/resources/config/application.yml b/system/src/main/resources/config/application.yml new file mode 100644 index 0000000..65b07b5 --- /dev/null +++ b/system/src/main/resources/config/application.yml @@ -0,0 +1,60 @@ +server: + port: 7000 + +spring: + freemarker: + check-template-location: false + profiles: + active: dev + jackson: + time-zone: GMT+8 + data: + redis: + repositories: + enabled: false + main: + allow-bean-definition-overriding: true + + #配置 Jpa + jpa: + properties: + hibernate: + ddl-auto: none + #dialect: org.hibernate.dialect.MySQL5InnoDBDialect + # naming: + # physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + open-in-view: true + + + redis: + #数据库索引 + database: ${REDIS_DB:0} + host: ${REDIS_HOST:192.168.99.207} + port: ${REDIS_PORT:6379} + password: ${REDIS_PWD:ftzn83560792} + #连接超时时间 + timeout: 5000 + +task: + pool: + # 核心线程池大小 + core-pool-size: 10 + # 最大线程数 + max-pool-size: 30 + # 活跃时间 + keep-alive-seconds: 60 + # 队列容量 + queue-capacity: 50 + +#七牛云 +qiniu: + # 文件大小 /M + max-size: 15 + +#邮箱验证码有效时间/秒 +code: + expiration: 300 + +#密码加密传输,前端公钥加密,后端私钥解密 +rsa: + private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== \ No newline at end of file