diff --git a/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerAutoConfiguration.java b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerAutoConfiguration.java new file mode 100644 index 0000000..73afe16 --- /dev/null +++ b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerAutoConfiguration.java @@ -0,0 +1,123 @@ +package com.jing.common.swagger.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +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; + +@Configuration +@EnableSwagger2 +@EnableConfigurationProperties(SwaggerProperties.class) +@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true) +@Import({SwaggerBeanPostProcessor.class, SwaggerWebConfiguration.class}) +public class SwaggerAutoConfiguration +{ + /** + * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点 + */ + private static final List DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**"); + + private static final String BASE_PATH = "/**"; + + @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/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerBeanPostProcessor.java b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerBeanPostProcessor.java new file mode 100644 index 0000000..24fdb1a --- /dev/null +++ b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerBeanPostProcessor.java @@ -0,0 +1,52 @@ +package com.jing.common.swagger.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +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; + +/** + * swagger 在 springboot 2.6.x 不兼容问题的处理 + * + * @author ruoyi + */ +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/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerProperties.java b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerProperties.java new file mode 100644 index 0000000..5eef3b1 --- /dev/null +++ b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerProperties.java @@ -0,0 +1,343 @@ +package com.jing.common.swagger.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@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; + } + } +} \ No newline at end of file diff --git a/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerWebConfiguration.java b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerWebConfiguration.java new file mode 100644 index 0000000..c3bd8be --- /dev/null +++ b/jing-common/jing-common-swagger/src/main/java/com/jing/common/swagger/config/SwaggerWebConfiguration.java @@ -0,0 +1,20 @@ +package com.jing.common.swagger.config; + +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * swagger 资源映射路径 + * + * @author ruoyi + */ +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/"); + } +} \ No newline at end of file diff --git a/jing-modules/jing-gen/src/main/resources/vm/java/sub-domain.java.vm b/jing-modules/jing-gen/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..0c98e40 --- /dev/null +++ b/jing-modules/jing-gen/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,76 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.jing.common.core.annotation.Excel; +import com.jing.common.core.web.domain.BaseEntity; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/jing-modules/jing-system/src/main/java/com/jing/system/controller/SysConfigController.java b/jing-modules/jing-system/src/main/java/com/jing/system/controller/SysConfigController.java new file mode 100644 index 0000000..bc2a8cf --- /dev/null +++ b/jing-modules/jing-system/src/main/java/com/jing/system/controller/SysConfigController.java @@ -0,0 +1,133 @@ +package com.jing.system.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.jing.common.core.utils.poi.ExcelUtil; +import com.jing.common.core.web.controller.BaseController; +import com.jing.common.core.web.domain.AjaxResult; +import com.jing.common.core.web.page.TableDataInfo; +import com.jing.common.log.annotation.Log; +import com.jing.common.log.enums.BusinessType; +import com.jing.common.security.annotation.RequiresPermissions; +import com.jing.common.security.utils.SecurityUtils; +import com.jing.system.domain.SysConfig; +import com.jing.system.service.ISysConfigService; + +/** + * 参数配置 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/config") +public class SysConfigController extends BaseController +{ + @Autowired + private ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @RequiresPermissions("system:config:list") + @GetMapping("/list") + public TableDataInfo list(SysConfig config) + { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @RequiresPermissions("system:config:export") + @PostMapping("/export") + public void export(HttpServletResponse response, SysConfig config) + { + List list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @RequiresPermissions("system:config:add") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(SecurityUtils.getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @RequiresPermissions("system:config:edit") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @RequiresPermissions("system:config:remove") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) + { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @RequiresPermissions("system:config:remove") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return success(); + } +} diff --git a/jing-modules/jing-system/src/main/java/com/jing/system/domain/SysConfig.java b/jing-modules/jing-system/src/main/java/com/jing/system/domain/SysConfig.java new file mode 100644 index 0000000..bc64ce3 --- /dev/null +++ b/jing-modules/jing-system/src/main/java/com/jing/system/domain/SysConfig.java @@ -0,0 +1,111 @@ +package com.jing.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.jing.common.core.annotation.Excel; +import com.jing.common.core.annotation.Excel.ColumnType; +import com.jing.common.core.web.domain.BaseEntity; + +/** + * 参数配置表 sys_config + * + * @author ruoyi + */ +public class SysConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 参数主键 */ + @Excel(name = "参数主键", cellType = ColumnType.NUMERIC) + private Long configId; + + /** 参数名称 */ + @Excel(name = "参数名称") + private String configName; + + /** 参数键名 */ + @Excel(name = "参数键名") + private String configKey; + + /** 参数键值 */ + @Excel(name = "参数键值") + private String configValue; + + /** 系统内置(Y是 N否) */ + @Excel(name = "系统内置", readConverterExp = "Y=是,N=否") + private String configType; + + public Long getConfigId() + { + return configId; + } + + public void setConfigId(Long configId) + { + this.configId = configId; + } + + @NotBlank(message = "参数名称不能为空") + @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") + public String getConfigName() + { + return configName; + } + + public void setConfigName(String configName) + { + this.configName = configName; + } + + @NotBlank(message = "参数键名长度不能为空") + @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") + public String getConfigKey() + { + return configKey; + } + + public void setConfigKey(String configKey) + { + this.configKey = configKey; + } + + @NotBlank(message = "参数键值不能为空") + @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") + public String getConfigValue() + { + return configValue; + } + + public void setConfigValue(String configValue) + { + this.configValue = configValue; + } + + public String getConfigType() + { + return configType; + } + + public void setConfigType(String configType) + { + this.configType = configType; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("configId", getConfigId()) + .append("configName", getConfigName()) + .append("configKey", getConfigKey()) + .append("configValue", getConfigValue()) + .append("configType", getConfigType()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/jing-ui/src/assets/icons/svg/swagger.svg b/jing-ui/src/assets/icons/svg/swagger.svg new file mode 100644 index 0000000..05d4e7b --- /dev/null +++ b/jing-ui/src/assets/icons/svg/swagger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jing-ui/src/assets/icons/svg/switch.svg b/jing-ui/src/assets/icons/svg/switch.svg new file mode 100644 index 0000000..0ba61e3 --- /dev/null +++ b/jing-ui/src/assets/icons/svg/switch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jing-ui/src/assets/icons/svgo.yml b/jing-ui/src/assets/icons/svgo.yml new file mode 100644 index 0000000..d11906a --- /dev/null +++ b/jing-ui/src/assets/icons/svgo.yml @@ -0,0 +1,22 @@ +# replace default config + +# multipass: true +# full: true + +plugins: + + # - name + # + # or: + # - name: false + # - name: true + # + # or: + # - name: + # param1: 1 + # param2: 2 + +- removeAttrs: + attrs: + - 'fill' + - 'fill-rule'