diff --git a/ecell-internationalize/ecell-internationalize-auth/pom.xml b/ecell-internationalize/ecell-internationalize-auth/pom.xml new file mode 100644 index 0000000..a472697 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/pom.xml @@ -0,0 +1,79 @@ + + + + ecell-internationalize + com.ecell.internationalize + 1.0-SNAPSHOT + + 4.0.0 + + com.ecell.internationalize.auth + ecell-internationalize-auth + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + com.ecell.internationalize.common.swagger + ecell-internationalize-swagger + 1.0-SNAPSHOT + + + com.ecell.internationalize.common.core + ecell-internationalize-core + 1.0-SNAPSHOT + + + + com.ecell.internationalize.common.security + ecell-internationalize-security + 1.0-SNAPSHOT + + + + + org.projectlombok + lombok + true + + + + \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/EcellAuthApplication.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/EcellAuthApplication.java new file mode 100644 index 0000000..dd4dfe7 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/EcellAuthApplication.java @@ -0,0 +1,27 @@ +package com.ecell.internationalize.auth; + +import com.ecell.internationalize.auth.config.ExceptionAdviceConfig; +import com.ecell.internationalize.common.core.utils.SpringUtils; +import com.ecell.internationalize.common.swagger.annotation.EnableCustomSwagger2; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * @author borui + */ + +@EnableFeignClients +@EnableCustomSwagger2 +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class },scanBasePackages = {"com.ecell.internationalize"}) +public class EcellAuthApplication { + public static void main(String[] args) + { + SpringApplication.run(EcellAuthApplication.class, args); + + + System.out.println("(♥◠‿◠)ノ゙ 认证授权中心启动成功 ლ(´ڡ`ლ)゙ \n"); + } +} diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/config/ExceptionAdviceConfig.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/config/ExceptionAdviceConfig.java new file mode 100644 index 0000000..49dfcc6 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/config/ExceptionAdviceConfig.java @@ -0,0 +1,40 @@ +package com.ecell.internationalize.auth.config; + +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.SpringUtils; +import com.ecell.internationalize.common.core.utils.locale.LocaleUtil; +import com.ecell.internationalize.common.core.web.domain.AjaxResult; +import net.bytebuddy.implementation.bytecode.constant.FieldConstant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.http.HttpRequest; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Locale; + +/** + * @author borui + */ +@RestControllerAdvice +public class ExceptionAdviceConfig { + private static final Logger logger = LoggerFactory.getLogger(ExceptionAdviceConfig.class); + /** + * 全局异常处理,配置国际化 + * @Author: liy + * @Date: 2022/7/8 13:53 + */ + @ExceptionHandler(Exception.class) + public AjaxResult exceptionHandler(Exception e) { + e.printStackTrace(); + +// Locale locale = LocaleUtil.getLocale(); +// +// MessageSource messageSource = SpringUtils.getBean(MessageSource.class); +// String message = messageSource.getMessage("messages.login.empty", null, locale); + String message = LocaleUtil.getMessage("messages.login.empty"); + System.out.println("获取到的国际化语言是:=="+message); + return AjaxResult.error(message); + } +} diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/controller/AuthController.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/controller/AuthController.java new file mode 100644 index 0000000..a3d484c --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/controller/AuthController.java @@ -0,0 +1,20 @@ +package com.ecell.internationalize.auth.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author borui + */ +@RestController +@RequestMapping("/auth") +public class AuthController { + + @GetMapping("test") + public String test( ){ + int a=1; + int b= a/0; + return "111"; + } +} diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/resources/application-dev.yml b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/application-dev.yml new file mode 100644 index 0000000..eca5750 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/application-dev.yml @@ -0,0 +1,33 @@ +spring: + redis: + host: 120.77.209.176 + port: 6379 + database: 2 + password: Ecell...20201001 + # 配置国际化资源文件路径 + messages: + basename: i18n/messages + encoding: UTF-8 + #设置静态资源路径,多个以逗号分隔 + web: + resources: + static-locations: classpath:static/ +mybatis: + #配置SQL映射文件路径 + mapper-locations: classpath:mapper/*.xml + # 搜索指定包别名 + typeAliasesPackage: com.yisai.auth + #驼峰命名 + configuration: + map-underscore-to-camel-case: true +#开启熔断降级 +feign: + sentinel: + enabled: true + client: + config: + default: + connectTimeout: 10000 + readTimeout: 60000 + + diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/resources/bootstrap.yml b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..848ce9f --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/bootstrap.yml @@ -0,0 +1,25 @@ +# Tomcat +server: + port: 9998 + +# Spring +spring: + application: + # 应用名称 + name: ecell-internationalize-auth + profiles: + # 环境配置 + active: dev + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8848 + config: + # 配置中心地址 + server-addr: 127.0.0.1:8848 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages.properties b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..22fd134 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages.properties @@ -0,0 +1,8 @@ +messages.login.empty=账号或者密码不能为空 +messages.fallback.info=服务不存在或网络端口错误 +messages.login.error=用户名不存在 +messages.account.delete=您的账号已被删除 +messages.account.stop=您的账号已被禁用 +messages.success=操作成功 +messages.error=操作失败,请联系管理员处理 +messages.error.password=输入的密码错误 \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_en_US.properties b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000..4281241 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_en_US.properties @@ -0,0 +1,8 @@ +messages.login.empty=Account or password cannot be empty +messages.fallback.info=Service does not exist or network port error +messages.login.error=user name does not exist +messages.account.delete=Your account has been deleted +messages.account.stop=Your account has been disabled +messages.success=Operation successful +messages.error=Operation failed, please contact the administrator for handling +messages.error.password=Wrong password entered \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_ru_RU.properties b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_ru_RU.properties new file mode 100644 index 0000000..1b997a4 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_ru_RU.properties @@ -0,0 +1,8 @@ +messages.fallback.info=\u0421\u0435\u0440\u0432\u0438\u0441\u044B \u043D\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0442 \u0438\u043B\u0438 \u043E\u0448\u0438\u0431\u043A\u0438 \u0441\u0435\u0442\u0435\u0432\u044B\u0445 \u043F\u043E\u0440\u0442\u043E\u0432 +messages.login.error=\u0418\u043C\u0435\u043D\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u043D\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 +messages.account.stop=\u0412\u0430\u0448 \u0430\u043A\u043A\u0430\u0443\u043D\u0442 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D +messages.error.password=\u0412\u0432\u0435\u0434\u0435\u043D\u0430 \u043E\u0448\u0438\u0431\u043A\u0430 \u0432 \u043A\u043E\u0434\u0435 +messages.success=\u041E\u043F\u0435\u0440\u0430\u0446\u0438\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0430. +messages.account.delete=\u0412\u0430\u0448 \u0430\u043A\u043A\u0430\u0443\u043D\u0442 \u0431\u044B\u043B \u0443\u0434\u0430\u043B\u0435\u043D +messages.login.empty=\u041D\u043E\u043C\u0435\u0440 \u0441\u0447\u0435\u0442\u0430 \u0438\u043B\u0438 \u043F\u0430\u0440\u043E\u043B\u044C \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C +messages.error=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u0441\u0432\u044F\u0436\u0438\u0442\u0435\u0441\u044C \u0441 \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u043E\u043C diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_zh_CN.properties b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..22fd134 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,8 @@ +messages.login.empty=账号或者密码不能为空 +messages.fallback.info=服务不存在或网络端口错误 +messages.login.error=用户名不存在 +messages.account.delete=您的账号已被删除 +messages.account.stop=您的账号已被禁用 +messages.success=操作成功 +messages.error=操作失败,请联系管理员处理 +messages.error.password=输入的密码错误 \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/pom.xml b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/pom.xml index 8922171..3b6ca49 100644 --- a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/pom.xml +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/pom.xml @@ -120,6 +120,13 @@ swagger-annotations + + + org.projectlombok + lombok + true + + \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/LoginUser.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/LoginUser.java new file mode 100644 index 0000000..3288acf --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/LoginUser.java @@ -0,0 +1,59 @@ +package com.ecell.internationalize.common.core.domain; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Set; + +/** + * @author borui + */ +@Data +public class LoginUser implements Serializable { + private static final long serialVersionUID = 1L; + /** + * 用户唯一标识 + */ + private String token; + + /** + * 用户名id + */ + private Long userid; + + /** + * 用户名 + */ + private String username; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 角色列表 + */ + private Set roles; + + /** + * 用户信息 + */ + private SysUser sysUser; + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/SysRole.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/SysRole.java new file mode 100644 index 0000000..352c9fa --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/SysRole.java @@ -0,0 +1,60 @@ +package com.ecell.internationalize.common.core.domain; + +import com.ecell.internationalize.common.core.annotation.Excel; +import com.ecell.internationalize.common.core.web.domain.BaseEntity; +import lombok.Data; + +/** + * @author borui + */ +@Data +public class SysRole extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @Excel(name = "角色序号", cellType = Excel.ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @Excel(name = "角色排序") + private String roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + private Long[] menuIds; + + /** 部门组(数据权限) */ + private Long[] deptIds; + + public SysRole() + { + + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/SysUser.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/SysUser.java new file mode 100644 index 0000000..b054d59 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/domain/SysUser.java @@ -0,0 +1,98 @@ +package com.ecell.internationalize.common.core.domain; + +import com.ecell.internationalize.common.core.annotation.Excel; +import com.ecell.internationalize.common.core.annotation.Excels; +import com.ecell.internationalize.common.core.web.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * @author borui + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@ApiModel(value = "SysUser对象", description = "系统用户") +public class SysUser extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Excel(name = "用户序号", cellType = Excel.ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @Excel(name = "部门编号", type = Excel.Type.IMPORT) + private Long deptId; + + /** 用户账号 */ + @Excel(name = "登录名称") + private String userName; + + /** 用户昵称 */ + @Excel(name = "用户名称") + private String nickName; + + /** 用户邮箱 */ + @Excel(name = "用户邮箱") + private String email; + + /** 手机号码 */ + @Excel(name = "手机号码") + private String phonenumber; + + /** 用户性别 */ + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + private String avatar; + + /** 密码 */ + private String password; + + /** 帐号状态(0正常 1停用) */ + @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 最后登录IP */ + @Excel(name = "最后登录IP", type = Excel.Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT) + private Date loginDate; + + /** 部门对象 */ + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Excel.Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Excel.Type.EXPORT) + }) + + /** 角色对象 */ + private List roles; + + /** 角色组 */ + private Long[] roleIds; + + /** 岗位组 */ + private Long[] postIds; + + /** 角色ID */ + private Long roleId; + + public SysUser() + { + + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/enums/UserStatus.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/enums/UserStatus.java index de741f7..03926f5 100644 --- a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/enums/UserStatus.java +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/enums/UserStatus.java @@ -5,23 +5,23 @@ package com.ecell.internationalize.common.core.enums; * @author borui */ public class UserStatus { - OK("0", "正常"),DISABLE("1", "停用"),DELETED("2", "删除"); - private final String code; - private final String info; - - UserStatus(String code, String info) - { - this.code = code; - this.info = info; - } - - public String getCode() - { - return code; - } - - public String getInfo() - { - return info; - } +// OK("0", "正常"),DISABLE("1", "停用"),DELETED("2", "删除"); +// private final String code; +// private final String info; +// +// UserStatus(String code, String info) +// { +// this.code = code; +// this.info = info; +// } +// +// public String getCode() +// { +// return code; +// } +// +// public String getInfo() +// { +// return info; +// } } diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java index 8ee49bd..3a76b94 100644 --- a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java @@ -1,6 +1,10 @@ package com.ecell.internationalize.common.core.utils.locale; +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.SpringUtils; import com.ecell.internationalize.common.core.utils.StringUtils; +import org.springframework.context.MessageSource; +import org.springframework.http.HttpRequest; import java.util.Locale; @@ -8,13 +12,16 @@ import java.util.Locale; * @author borui */ public class LocaleUtil { - public static Locale getLocale(String lang){ + + public static Locale getLocale(){ + String language = ServletUtils.getHeader(ServletUtils.getRequest(), "content-language"); + Locale locale; - if (StringUtils.isEmpty(lang)) { + if (StringUtils.isEmpty(language)) { locale = Locale.US; } else { try { - String [] split = lang.split("_"); + String [] split = language.split("_"); locale = new Locale(split[0], split[1]); } catch (Exception e) { locale = Locale.US; @@ -22,4 +29,11 @@ public class LocaleUtil { } return locale; } + + public static String getMessage(String code) { + Locale locale = getLocale(); + return SpringUtils.getBean(MessageSource.class).getMessage(code, null, locale); + + } + } diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/resources/META-INF/spring.factories b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/resources/META-INF/spring.factories index ff50205..bcd4116 100644 --- a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/resources/META-INF/spring.factories +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/resources/META-INF/spring.factories @@ -1,4 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.ruoyi.common.core.utils.SpringUtils + com.ecell.internationalize.common.core.utils.SpringUtils diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/pom.xml b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/pom.xml index 3b57ea8..f6b1cfb 100644 --- a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/pom.xml +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/pom.xml @@ -12,9 +12,23 @@ com.ecell.internationalize.common.security ecell-internationalize-security - - 8 - 8 - + + + + org.springframework + spring-webmvc + + + com.ecell.internationalize.common.redis + ecell-internationalize-redis + 1.0-SNAPSHOT + + + + org.projectlombok + lombok + true + + \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/EnableCustomConfig.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/EnableCustomConfig.java new file mode 100644 index 0000000..05dae41 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/EnableCustomConfig.java @@ -0,0 +1,29 @@ +package com.ecell.internationalize.common.security.annotation; + +import com.ecell.internationalize.common.security.config.ApplicationConfig; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.cloud.openfeign.FeignAutoConfiguration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.lang.annotation.*; + +/** + * @author borui + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.campus.**.mapper") +// 开启线程异步执行 +@EnableAsync +// 自动加载类 +@Import({ ApplicationConfig.class, FeignAutoConfiguration.class }) +public @interface EnableCustomConfig { + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/InnerAuth.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/InnerAuth.java new file mode 100644 index 0000000..c852588 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/InnerAuth.java @@ -0,0 +1,16 @@ +package com.ecell.internationalize.common.security.annotation; + +import java.lang.annotation.*; + +/** + * @author borui + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InnerAuth { + /** + * 是否校验用户信息 + */ + boolean isUser() default false; +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/Logical.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/Logical.java new file mode 100644 index 0000000..2d65b35 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/Logical.java @@ -0,0 +1,18 @@ +package com.ecell.internationalize.common.security.annotation; + +/** + * @author borui + */ + +public enum Logical { + /** + * 必须具有所有的元素 + */ + AND, + + /** + * 只需具有其中一个元素 + */ + OR + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresLogin.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresLogin.java new file mode 100644 index 0000000..5cac6d1 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresLogin.java @@ -0,0 +1,15 @@ +package com.ecell.internationalize.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author borui + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface RequiresLogin { + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresPermissions.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresPermissions.java new file mode 100644 index 0000000..87416b8 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresPermissions.java @@ -0,0 +1,23 @@ +package com.ecell.internationalize.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author borui + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface RequiresPermissions { + /** + * 需要校验的权限码 + */ + String[] value() default {}; + + /** + * 验证模式:AND | OR,默认AND + */ + Logical logical() default Logical.AND; +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresRoles.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresRoles.java new file mode 100644 index 0000000..6e5e6c5 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/annotation/RequiresRoles.java @@ -0,0 +1,23 @@ +package com.ecell.internationalize.common.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author borui + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface RequiresRoles { + /** + * 需要校验的角色标识 + */ + String[] value() default {}; + + /** + * 验证逻辑:AND | OR,默认AND + */ + Logical logical() default Logical.AND; +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/aspect/InnerAuthAspect.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/aspect/InnerAuthAspect.java new file mode 100644 index 0000000..7d363c3 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/aspect/InnerAuthAspect.java @@ -0,0 +1,44 @@ +package com.ecell.internationalize.common.security.aspect; + +import com.ecell.internationalize.common.core.constant.SecurityConstants; +import com.ecell.internationalize.common.core.exception.InnerAuthException; +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.security.annotation.InnerAuth; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +/** + * @author borui + */ +@Aspect +@Component +public class InnerAuthAspect implements Ordered { + @Around("@annotation(innerAuth)") + public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable + { + String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE); + // 内部请求验证 + if (!StringUtils.equals(SecurityConstants.INNER, source)) + { + throw new InnerAuthException("没有内部访问权限,不允许访问"); + } + + String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID); + String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME); + // 用户信息验证 + if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))) + { + throw new InnerAuthException("没有设置用户信息,不允许访问 "); + } + return point.proceed(); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 1; + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/aspect/PreAuthorizeAspect.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/aspect/PreAuthorizeAspect.java new file mode 100644 index 0000000..401f173 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/aspect/PreAuthorizeAspect.java @@ -0,0 +1,89 @@ +package com.ecell.internationalize.common.security.aspect; + +import com.ecell.internationalize.common.security.annotation.RequiresLogin; +import com.ecell.internationalize.common.security.annotation.RequiresPermissions; +import com.ecell.internationalize.common.security.annotation.RequiresRoles; +import com.ecell.internationalize.common.security.auth.AuthUtil; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/** + * @author borui + */ +@Aspect +@Component +public class PreAuthorizeAspect { + /** + * 定义AOP签名 (切入所有使用鉴权注解的方法) + */ + public static final String POINTCUT_SIGN = " @annotation(com.ecell.internationalize.common.security.annotation.RequiresLogin) || " + + "@annotation(com.ecell.internationalize.common.security.annotation.RequiresPermissions) || " + + "@annotation(com.ecell.internationalize.common.security.annotation.RequiresRoles)"; + + /** + * 声明AOP签名 + */ + @Pointcut(POINTCUT_SIGN) + public void pointcut() { + } + + /** + * 环绕切入 + * + * @param joinPoint 切面对象 + * @return 底层方法执行后的返回值 + * @throws Throwable 底层方法抛出的异常 + */ + @Around("pointcut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable + { + // 注解鉴权 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + checkMethodAnnotation(signature.getMethod()); + try + { + // 执行原有逻辑 + Object obj = joinPoint.proceed(); + return obj; + } + catch (Throwable e) + { + throw e; + } + } + + + /** + * 对一个Method对象进行注解检查 + */ + public void checkMethodAnnotation(Method method) + { + // 校验 @RequiresLogin 注解 + RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class); + if (requiresLogin != null) + { + AuthUtil.checkLogin(); + } + + // 校验 @RequiresRoles 注解 + RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class); + if (requiresRoles != null) + { + AuthUtil.checkRole(requiresRoles); + } + + // 校验 @RequiresPermissions 注解 + RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class); + if (requiresPermissions != null) + { + AuthUtil.checkPermi(requiresPermissions); + } + } + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/auth/AuthLogic.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/auth/AuthLogic.java new file mode 100644 index 0000000..ae39784 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/auth/AuthLogic.java @@ -0,0 +1,371 @@ +package com.ecell.internationalize.common.security.auth; + +import com.ecell.internationalize.common.core.domain.LoginUser; +import com.ecell.internationalize.common.core.domain.SysUser; +import com.ecell.internationalize.common.core.exception.auth.NotLoginException; +import com.ecell.internationalize.common.core.exception.auth.NotPermissionException; +import com.ecell.internationalize.common.core.exception.auth.NotRoleException; +import com.ecell.internationalize.common.core.utils.SpringUtils; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.security.annotation.Logical; +import com.ecell.internationalize.common.security.annotation.RequiresLogin; +import com.ecell.internationalize.common.security.annotation.RequiresPermissions; +import com.ecell.internationalize.common.security.annotation.RequiresRoles; +import com.ecell.internationalize.common.security.service.TokenService; +import com.ecell.internationalize.common.security.utils.SecurityUtils; +import org.springframework.util.PatternMatchUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @author borui + */ + +public class AuthLogic { + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + public TokenService tokenService = SpringUtils.getBean(TokenService.class); + + /** + * 会话注销 + */ + public void logout() + { + String token = SecurityUtils.getToken(); + if (token == null) + { + return; + } + logoutByToken(token); + } + + /** + * 会话注销,根据指定Token + */ + public void logoutByToken(String token) + { + tokenService.delLoginUser(token); + } + + /** + * 检验用户是否已经登录,如未登录,则抛出异常 + */ + public void checkLogin() + { + getLoginUser(); + } + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @return 用户缓存信息 + */ + public LoginUser getLoginUser() + { + String token = SecurityUtils.getToken(); + if (token == null) + { + throw new NotLoginException("未提供token"); + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null) + { + throw new NotLoginException("无效的token"); + } + return loginUser; + } + + /** + * 获取当前用户缓存信息, 如果未登录,则抛出异常 + * + * @param token 前端传递的认证信息 + * @return 用户缓存信息 + */ + public LoginUser getLoginUser(String token) + { + return tokenService.getLoginUser(token); + } + + /** + * 验证当前用户有效期, 如果相差不足120分钟,自动刷新缓存 + * + * @param loginUser 当前用户信息 + */ + public void verifyLoginUserExpire(LoginUser loginUser) + { + tokenService.verifyToken(loginUser); + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) + { + return hasPermi(getPermiList(), permission); + } + + /** + * 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public void checkPermi(String permission) + { + if (!hasPermi(getPermiList(), permission)) + { + throw new NotPermissionException(permission); + } + } + + /** + * 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param requiresPermissions 注解对象 + */ + public void checkPermi(RequiresPermissions requiresPermissions) + { + if (requiresPermissions.logical() == Logical.AND) + { + checkPermiAnd(requiresPermissions.value()); + } + else + { + checkPermiOr(requiresPermissions.value()); + } + } + + /** + * 验证用户是否含有指定权限,必须全部拥有 + * + * @param permissions 权限列表 + */ + public void checkPermiAnd(String... permissions) + { + Set permissionList = getPermiList(); + for (String permission : permissions) + { + if (!hasPermi(permissionList, permission)) + { + throw new NotPermissionException(permission); + } + } + } + + /** + * 验证用户是否含有指定权限,只需包含其中一个 + * + * @param permissions 权限码数组 + */ + public void checkPermiOr(String... permissions) + { + Set permissionList = getPermiList(); + for (String permission : permissions) + { + if (hasPermi(permissionList, permission)) + { + return; + } + } + if (permissions.length > 0) + { + throw new NotPermissionException(permissions); + } + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色标识 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) + { + return hasRole(getRoleList(), role); + } + + /** + * 判断用户是否拥有某个角色, 如果验证未通过,则抛出异常: NotRoleException + * + * @param role 角色标识 + */ + public void checkRole(String role) + { + if (!hasRole(role)) + { + throw new NotRoleException(role); + } + } + + /** + * 根据注解(@RequiresRoles)鉴权 + * + * @param requiresRoles 注解对象 + */ + public void checkRole(RequiresRoles requiresRoles) + { + if (requiresRoles.logical() == Logical.AND) + { + checkRoleAnd(requiresRoles.value()); + } + else + { + checkRoleOr(requiresRoles.value()); + } + } + + /** + * 验证用户是否含有指定角色,必须全部拥有 + * + * @param roles 角色标识数组 + */ + public void checkRoleAnd(String... roles) + { + Set roleList = getRoleList(); + for (String role : roles) + { + if (!hasRole(roleList, role)) + { + throw new NotRoleException(role); + } + } + } + + /** + * 验证用户是否含有指定角色,只需包含其中一个 + * + * @param roles 角色标识数组 + */ + public void checkRoleOr(String... roles) + { + Set roleList = getRoleList(); + for (String role : roles) + { + if (hasRole(roleList, role)) + { + return; + } + } + if (roles.length > 0) + { + throw new NotRoleException(roles); + } + } + + /** + * 根据注解(@RequiresLogin)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation(RequiresLogin at) + { + this.checkLogin(); + } + + /** + * 根据注解(@RequiresRoles)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation(RequiresRoles at) + { + String[] roleArray = at.value(); + if (at.logical() == Logical.AND) + { + this.checkRoleAnd(roleArray); + } + else + { + this.checkRoleOr(roleArray); + } + } + + /** + * 根据注解(@RequiresPermissions)鉴权 + * + * @param at 注解对象 + */ + public void checkByAnnotation(RequiresPermissions at) + { + String[] permissionArray = at.value(); + if (at.logical() == Logical.AND) + { + this.checkPermiAnd(permissionArray); + } + else + { + this.checkPermiOr(permissionArray); + } + } + + /** + * 获取当前账号的角色列表 + * + * @return 角色列表 + */ + public Set getRoleList() + { + try + { + LoginUser loginUser = getLoginUser(); + return loginUser.getRoles(); + } + catch (Exception e) + { + return new HashSet<>(); + } + } + + /** + * 获取当前账号的权限列表 + * + * @return 权限列表 + */ + public Set getPermiList() + { + try + { + LoginUser loginUser = getLoginUser(); + return loginUser.getPermissions(); + } + catch (Exception e) + { + return new HashSet<>(); + } + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(Collection authorities, String permission) + { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * @return 用户是否具备某角色权限 + */ + public boolean hasRole(Collection roles, String role) + { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role)); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/auth/AuthUtil.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/auth/AuthUtil.java new file mode 100644 index 0000000..33ea8b4 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/auth/AuthUtil.java @@ -0,0 +1,161 @@ +package com.ecell.internationalize.common.security.auth; + +import com.ecell.internationalize.common.core.domain.LoginUser; +import com.ecell.internationalize.common.core.domain.SysUser; +import com.ecell.internationalize.common.security.annotation.RequiresPermissions; +import com.ecell.internationalize.common.security.annotation.RequiresRoles; + +/** + * @author borui + */ + +public class AuthUtil { + /** + * 底层的 AuthLogic 对象 + */ + public static AuthLogic authLogic = new AuthLogic(); + + /** + * 会话注销 + */ + public static void logout() + { + authLogic.logout(); + } + + /** + * 会话注销,根据指定Token + * + * @param tokenValue 指定token + */ + public static void logoutByToken(String token) + { + authLogic.logoutByToken(token); + } + + /** + * 检验当前会话是否已经登录,如未登录,则抛出异常 + */ + public static void checkLogin() + { + authLogic.checkLogin(); + } + + /** + * 获取当前登录用户信息 + */ + public static LoginUser getLoginUser(String token) + { + return authLogic.getLoginUser(token); + } + + /** + * 验证当前用户有效期 + */ + public static void verifyLoginUserExpire(LoginUser loginUser) + { + authLogic.verifyLoginUserExpire(loginUser); + } + + /** + * 当前账号是否含有指定角色标识, 返回true或false + * + * @param role 角色标识 + * @return 是否含有指定角色标识 + */ + public static boolean hasRole(String role) + { + return authLogic.hasRole(role); + } + + /** + * 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException + * + * @param role 角色标识 + */ + public static void checkRole(String role) + { + authLogic.checkRole(role); + } + + /** + * 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotRoleException + * + * @param requiresRoles 角色权限注解 + */ + public static void checkRole(RequiresRoles requiresRoles) + { + authLogic.checkRole(requiresRoles); + } + + /** + * 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] + * + * @param roles 角色标识数组 + */ + public static void checkRoleAnd(String... roles) + { + authLogic.checkRoleAnd(roles); + } + + /** + * 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] + * + * @param roles 角色标识数组 + */ + public static void checkRoleOr(String... roles) + { + authLogic.checkRoleOr(roles); + } + + /** + * 当前账号是否含有指定权限, 返回true或false + * + * @param permission 权限码 + * @return 是否含有指定权限 + */ + public static boolean hasPermi(String permission) + { + return authLogic.hasPermi(permission); + } + + /** + * 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param permission 权限码 + */ + public static void checkPermi(String permission) + { + authLogic.checkPermi(permission); + } + + /** + * 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException + * + * @param requiresPermissions 权限注解 + */ + public static void checkPermi(RequiresPermissions requiresPermissions) + { + authLogic.checkPermi(requiresPermissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,必须全部验证通过] + * + * @param permissions 权限码数组 + */ + public static void checkPermiAnd(String... permissions) + { + authLogic.checkPermiAnd(permissions); + } + + /** + * 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] + * + * @param permissions 权限码数组 + */ + public static void checkPermiOr(String... permissions) + { + authLogic.checkPermiOr(permissions); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/config/ApplicationConfig.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/config/ApplicationConfig.java new file mode 100644 index 0000000..3491b5a --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/config/ApplicationConfig.java @@ -0,0 +1,20 @@ +package com.ecell.internationalize.common.security.config; + +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; + +import java.util.TimeZone; + +/** + * @author borui + */ +public class ApplicationConfig { + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/config/WebMvcConfig.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/config/WebMvcConfig.java new file mode 100644 index 0000000..925c9ac --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/config/WebMvcConfig.java @@ -0,0 +1,30 @@ +package com.ecell.internationalize.common.security.config; + +import com.ecell.internationalize.common.security.interceptor.HeaderInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author borui + */ +public class WebMvcConfig implements WebMvcConfigurer { + /** 不需要拦截地址 */ + public static final String[] excludeUrls = { "/login", "/logout", "/refresh" }; + + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(getHeaderInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns(excludeUrls) + .order(-10); + } + + /** + * 自定义请求头拦截器 + */ + public HeaderInterceptor getHeaderInterceptor() + { + return new HeaderInterceptor(); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/feign/FeignAutoConfiguration.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/feign/FeignAutoConfiguration.java new file mode 100644 index 0000000..872ef46 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/feign/FeignAutoConfiguration.java @@ -0,0 +1,17 @@ +package com.ecell.internationalize.common.security.feign; + +import feign.RequestInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author borui + */ +@Configuration +public class FeignAutoConfiguration { + @Bean + public RequestInterceptor requestInterceptor() + { + return new FeignRequestInterceptor(); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/feign/FeignRequestInterceptor.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/feign/FeignRequestInterceptor.java new file mode 100644 index 0000000..b3265be --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/feign/FeignRequestInterceptor.java @@ -0,0 +1,48 @@ +package com.ecell.internationalize.common.security.feign; + +import com.ecell.internationalize.common.core.constant.SecurityConstants; +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.core.utils.ip.IpUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * @author borui + */ +@Component +public class FeignRequestInterceptor implements RequestInterceptor { + @Override + public void apply(RequestTemplate requestTemplate) + { + HttpServletRequest httpServletRequest = ServletUtils.getRequest(); + if (StringUtils.isNotNull(httpServletRequest)) + { + Map headers = ServletUtils.getHeaders(httpServletRequest); + // 传递用户信息请求头,防止丢失 + String userId = headers.get(SecurityConstants.DETAILS_USER_ID); + if (StringUtils.isNotEmpty(userId)) + { + requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId); + } + String userName = headers.get(SecurityConstants.DETAILS_USERNAME); + if (StringUtils.isNotEmpty(userName)) + { + requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName); + } + String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER); + if (StringUtils.isNotEmpty(authentication)) + { + requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication); + } + + // 配置客户端IP + requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr(ServletUtils.getRequest())); + } + } + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/handler/GlobalExceptionHandler.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..5aeb56c --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/handler/GlobalExceptionHandler.java @@ -0,0 +1,125 @@ +package com.ecell.internationalize.common.security.handler; + +import com.ecell.internationalize.common.core.constant.HttpStatus; +import com.ecell.internationalize.common.core.exception.InnerAuthException; +import com.ecell.internationalize.common.core.exception.ServiceException; +import com.ecell.internationalize.common.core.exception.auth.NotPermissionException; +import com.ecell.internationalize.common.core.exception.auth.NotRoleException; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.core.web.domain.AjaxResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author borui + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限码异常 + */ + @ExceptionHandler(NotPermissionException.class) + public AjaxResult handleNotPermissionException(NotPermissionException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 角色权限异常 + */ + @ExceptionHandler(NotRoleException.class) + public AjaxResult handleNotRoleException(NotRoleException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 内部认证异常 + */ + @ExceptionHandler(InnerAuthException.class) + public AjaxResult handleInnerAuthException(InnerAuthException e) + { + return AjaxResult.error(e.getMessage()); + } + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/interceptor/HeaderInterceptor.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/interceptor/HeaderInterceptor.java new file mode 100644 index 0000000..8508fff --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/interceptor/HeaderInterceptor.java @@ -0,0 +1,52 @@ +package com.ecell.internationalize.common.security.interceptor; + +import com.ecell.internationalize.common.core.constant.SecurityConstants; +import com.ecell.internationalize.common.core.context.SecurityContextHolder; +import com.ecell.internationalize.common.core.domain.LoginUser; +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.security.auth.AuthUtil; +import com.ecell.internationalize.common.security.utils.SecurityUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author borui + */ +public class HeaderInterceptor implements AsyncHandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + if (!(handler instanceof HandlerMethod)) + { + return true; + } + + SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID)); + SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME)); + SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY)); + + String token = SecurityUtils.getToken(); + if (StringUtils.isNotEmpty(token)) + { + LoginUser loginUser = AuthUtil.getLoginUser(token); + if (StringUtils.isNotNull(loginUser)) + { + AuthUtil.verifyLoginUserExpire(loginUser); + SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser); + } + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception + { + SecurityContextHolder.remove(); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/service/TokenService.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/service/TokenService.java new file mode 100644 index 0000000..5099663 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/service/TokenService.java @@ -0,0 +1,170 @@ +package com.ecell.internationalize.common.security.service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ecell.internationalize.common.core.constant.CacheConstants; +import com.ecell.internationalize.common.core.constant.SecurityConstants; +import com.ecell.internationalize.common.core.domain.LoginUser; +import com.ecell.internationalize.common.core.domain.SysUser; +import com.ecell.internationalize.common.core.utils.JwtUtils; +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.core.utils.ip.IpUtils; +import com.ecell.internationalize.common.core.utils.uuid.IdUtils; +import com.ecell.internationalize.common.redis.service.RedisService; +import com.ecell.internationalize.common.security.utils.SecurityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author borui + */ +@Component +public class TokenService { + @Autowired + private RedisService redisService; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private final static long expireTime = CacheConstants.EXPIRATION; + + private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; + + private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; + + /** + * 创建令牌 + */ + public Map createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + Long userId = loginUser.getSysUser().getUserId(); + String userName = loginUser.getSysUser().getUserName(); + loginUser.setToken(token); + loginUser.setUserid(userId); + loginUser.setUsername(userName); + loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); + refreshToken(loginUser); + + // Jwt存储信息 + Map claimsMap = new HashMap(); + claimsMap.put(SecurityConstants.USER_KEY, token); + claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); + claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); + + // 接口返回信息 + Map rspMap = new HashMap(); + rspMap.put("access_token", JwtUtils.createToken(claimsMap)); + rspMap.put("expires_in", expireTime); + return rspMap; + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser() + { + return getLoginUser(ServletUtils.getRequest()); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = SecurityUtils.getToken(request); + return getLoginUser(token); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(String token) + { + LoginUser user = null; + try + { + if (StringUtils.isNotEmpty(token)) + { + String userkey = JwtUtils.getUserKey(token); + user = redisService.getCacheObject(getTokenKey(userkey)); + return user; + } + } + catch (Exception e) + { + } + return user; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户缓存信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userkey = JwtUtils.getUserKey(token); + redisService.deleteObject(getTokenKey(userkey)); + } + } + + /** + * 验证令牌有效期,相差不足120分钟,自动刷新缓存 + * + * @param loginUser + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + private String getTokenKey(String token) + { + return ACCESS_TOKEN + token; + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/utils/SecurityUtils.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/utils/SecurityUtils.java new file mode 100644 index 0000000..78bbf34 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-security/src/main/java/com/ecell/internationalize/common/security/utils/SecurityUtils.java @@ -0,0 +1,159 @@ +package com.ecell.internationalize.common.security.utils; + +import com.ecell.internationalize.common.core.constant.SecurityConstants; +import com.ecell.internationalize.common.core.constant.TokenConstants; +import com.ecell.internationalize.common.core.context.SecurityContextHolder; +import com.ecell.internationalize.common.core.domain.LoginUser; +import com.ecell.internationalize.common.core.domain.SysUser; +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.StringUtils; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author borui + */ +public class SecurityUtils { + /** + * 获取用户ID + */ + public static Long getUserId() + { + return SecurityContextHolder.getUserId(); + } + + /** + * yisai获取用户ID + */ + public static String getStringUserId() + { + return SecurityContextHolder.getStringUserId(); + } + + + /** + * 获取用户名称 + */ + public static String getUsername() + { + return SecurityContextHolder.getUserName(); + } + + /** + * 获取用户key + */ + public static String getUserKey() + { + return SecurityContextHolder.getUserKey(); + } + + /** + * 获取登录用户信息 + */ + public static LoginUser getLoginUser() + { + return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class); + } + + /** + * 获取请求token + */ + public static String getToken() + { + return getToken(ServletUtils.getRequest()); + } + + /** + * 根据request获取请求token + */ + public static String getToken(HttpServletRequest request) + { + // 从header获取token标识 + String token = request.getHeader(TokenConstants.AUTHENTICATION); + return replaceTokenPrefix(token); + } + + /** + * 裁剪token前缀 + */ + public static String replaceTokenPrefix(String token) + { + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) + { + token = token.replaceFirst(TokenConstants.PREFIX, ""); + } + return token; + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + + /** + * yisai是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdminString(String userId) + { + return userId != null && "1".equals(userId); + } + + /** + * yisai是否为厂商 + * + * @param firmFlag + * @return 结果 + */ + public static boolean isFirm(String firmFlag) + { + return firmFlag != null && "1".equals(firmFlag); + } + + /** + * yisai是否为代理商 + * @param firmFlag + * @return 结果 + */ + public static boolean isAgent(String firmFlag) + { + return firmFlag != null && "2".equals(firmFlag); + } + + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/pom.xml b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/pom.xml index 6fd6023..4d297d6 100644 --- a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/pom.xml +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/pom.xml @@ -17,4 +17,21 @@ 8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + io.springfox + springfox-swagger2 + ${swagger.fox.version} + + + + \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/annotation/EnableCustomSwagger2.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/annotation/EnableCustomSwagger2.java new file mode 100644 index 0000000..e47424d --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/annotation/EnableCustomSwagger2.java @@ -0,0 +1,18 @@ +package com.ecell.internationalize.common.swagger.annotation; + +import com.ecell.internationalize.common.swagger.config.SwaggerAutoConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * @author borui + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import({ SwaggerAutoConfiguration.class }) +public @interface EnableCustomSwagger2 { + +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerAutoConfiguration.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerAutoConfiguration.java new file mode 100644 index 0000000..5f04253 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerAutoConfiguration.java @@ -0,0 +1,128 @@ +package com.ecell.internationalize.common.swagger.config; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.ApiSelectorBuilder; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +/** + * @author borui + */ + +@Configuration +@EnableSwagger2 +@EnableAutoConfiguration +@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true) +public class SwaggerAutoConfiguration { + /** + * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点 + */ + private static final List DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**"); + + private static final String BASE_PATH = "/**"; + + @Bean + @ConditionalOnMissingBean + public SwaggerProperties swaggerProperties() + { + return new SwaggerProperties(); + } + + @Bean + public Docket api(SwaggerProperties swaggerProperties) + { + // base-path处理 + if (swaggerProperties.getBasePath().isEmpty()) + { + swaggerProperties.getBasePath().add(BASE_PATH); + } + // noinspection unchecked + List> basePath = new ArrayList>(); + swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path))); + + // exclude-path处理 + if (swaggerProperties.getExcludePath().isEmpty()) + { + swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH); + } + + List> excludePath = new ArrayList<>(); + swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path))); + + ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2).host(swaggerProperties.getHost()) + .apiInfo(apiInfo(swaggerProperties)).select() + .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage())); + + swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p))); + swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate())); + + return builder.build().securitySchemes(securitySchemes()).securityContexts(securityContexts()).pathMapping("/"); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List securitySchemes() + { + List apiKeyList = new ArrayList(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", "header")); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List securityContexts() + { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的全局鉴权策略 + * + * @return + */ + private List defaultAuth() + { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + private ApiInfo apiInfo(SwaggerProperties swaggerProperties) + { + return new ApiInfoBuilder() + .title(swaggerProperties.getTitle()) + .description(swaggerProperties.getDescription()) + .license(swaggerProperties.getLicense()) + .licenseUrl(swaggerProperties.getLicenseUrl()) + .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl()) + .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail())) + .version(swaggerProperties.getVersion()) + .build(); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerBeanPostProcessor.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerBeanPostProcessor.java new file mode 100644 index 0000000..472ca49 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerBeanPostProcessor.java @@ -0,0 +1,45 @@ +package com.ecell.internationalize.common.swagger.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author borui + */ +@Component +public class SwaggerBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private void customizeSpringfoxHandlerMappings(List mappings) { + List copy = mappings.stream().filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(copy); + } + + @SuppressWarnings("unchecked") + private List getHandlerMappings(Object bean) { + try { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + field.setAccessible(true); + return (List) field.get(bean); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerProperties.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerProperties.java new file mode 100644 index 0000000..280fddf --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerProperties.java @@ -0,0 +1,301 @@ +package com.ecell.internationalize.common.swagger.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author borui + */ +@Component +@ConfigurationProperties("swagger") +public class SwaggerProperties { + /** + * 是否开启swagger + */ + private Boolean enabled; + + /** + * swagger会解析的包路径 + **/ + private String basePackage = ""; + + /** + * swagger会解析的url规则 + **/ + private List basePath = new ArrayList<>(); + + /** + * 在basePath基础上需要排除的url规则 + **/ + private List excludePath = new ArrayList<>(); + + /** + * 标题 + **/ + private String title = ""; + + /** + * 描述 + **/ + private String description = ""; + + /** + * 版本 + **/ + private String version = ""; + + /** + * 许可证 + **/ + private String license = ""; + + /** + * 许可证URL + **/ + private String licenseUrl = ""; + + /** + * 服务条款URL + **/ + private String termsOfServiceUrl = ""; + + /** + * host信息 + **/ + private String host = ""; + + /** + * 联系人信息 + */ + private Contact contact = new Contact(); + + /** + * 全局统一鉴权配置 + **/ + private Authorization authorization = new Authorization(); + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getBasePackage() { + return basePackage; + } + + public void setBasePackage(String basePackage) { + this.basePackage = basePackage; + } + + public List getBasePath() { + return basePath; + } + + public void setBasePath(List basePath) { + this.basePath = basePath; + } + + public List getExcludePath() { + return excludePath; + } + + public void setExcludePath(List excludePath) { + this.excludePath = excludePath; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public String getLicenseUrl() { + return licenseUrl; + } + + public void setLicenseUrl(String licenseUrl) { + this.licenseUrl = licenseUrl; + } + + public String getTermsOfServiceUrl() { + return termsOfServiceUrl; + } + + public void setTermsOfServiceUrl(String termsOfServiceUrl) { + this.termsOfServiceUrl = termsOfServiceUrl; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Contact getContact() { + return contact; + } + + public void setContact(Contact contact) { + this.contact = contact; + } + + public Authorization getAuthorization() { + return authorization; + } + + public void setAuthorization(Authorization authorization) { + this.authorization = authorization; + } + + public static class Contact { + /** + * 联系人 + **/ + private String name = ""; + /** + * 联系人url + **/ + private String url = ""; + /** + * 联系人email + **/ + private String email = ""; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + } + + public static class Authorization { + /** + * 鉴权策略ID,需要和SecurityReferences ID保持一致 + */ + private String name = ""; + + /** + * 需要开启鉴权URL的正则 + */ + private String authRegex = "^.*$"; + + /** + * 鉴权作用域列表 + */ + private List authorizationScopeList = new ArrayList<>(); + + private List tokenUrlList = new ArrayList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAuthRegex() { + return authRegex; + } + + public void setAuthRegex(String authRegex) { + this.authRegex = authRegex; + } + + public List getAuthorizationScopeList() { + return authorizationScopeList; + } + + public void setAuthorizationScopeList(List authorizationScopeList) { + this.authorizationScopeList = authorizationScopeList; + } + + public List getTokenUrlList() { + return tokenUrlList; + } + + public void setTokenUrlList(List tokenUrlList) { + this.tokenUrlList = tokenUrlList; + } + } + + public static class AuthorizationScope { + /** + * 作用域名称 + */ + private String scope = ""; + + /** + * 作用域描述 + */ + private String description = ""; + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerWebConfiguration.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerWebConfiguration.java new file mode 100644 index 0000000..d9f98cd --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-swagger/src/main/java/com/ecell/internationalize/common/swagger/config/SwaggerWebConfiguration.java @@ -0,0 +1,20 @@ +package com.ecell.internationalize.common.swagger.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author borui + */ +@Configuration +public class SwaggerWebConfiguration implements WebMvcConfigurer { + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** swagger-ui 地址 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + } + +} diff --git a/ecell-internationalize/pom.xml b/ecell-internationalize/pom.xml index fda20e7..3b9f949 100644 --- a/ecell-internationalize/pom.xml +++ b/ecell-internationalize/pom.xml @@ -12,6 +12,7 @@ ecell-internationalize-app ecell-internationalize-common ecell-internationalize-gateway + ecell-internationalize-auth