From 18a8855edba9e6ca946e2d68ee8009900c98bd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B6=E8=90=BD=E6=98=9F=E6=B6=AF?= <781958167@qq.com> Date: Thu, 14 May 2020 09:56:00 +0800 Subject: [PATCH] =?UTF-8?q?init=EF=BC=8C=E5=89=8D=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E5=88=86=E7=A6=BB=E5=8D=9A=E5=AE=A2=E9=A1=B9=E7=9B=AE=E6=A1=88?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + vueblog-java/.gitignore | 31 +++++ vueblog-java/pom.xml | 107 +++++++++++++++ .../main/java/com/markerhub/Application.java | 14 ++ .../java/com/markerhub/CodeGenerator.java | 116 ++++++++++++++++ .../com/markerhub/common/dto/LoginDto.java | 17 +++ .../exception/GlobalExcepitonHandler.java | 61 +++++++++ .../com/markerhub/common/lang/Result.java | 42 ++++++ .../java/com/markerhub/config/CorsConfig.java | 19 +++ .../markerhub/config/MybatisPlusConfig.java | 19 +++ .../com/markerhub/config/ShiroConfig.java | 110 ++++++++++++++++ .../controller/AccountController.java | 55 ++++++++ .../markerhub/controller/BlogController.java | 34 +++++ .../main/java/com/markerhub/entity/Blog.java | 42 ++++++ .../main/java/com/markerhub/entity/User.java | 49 +++++++ .../java/com/markerhub/mapper/BlogMapper.java | 16 +++ .../java/com/markerhub/mapper/UserMapper.java | 16 +++ .../com/markerhub/service/BlogService.java | 16 +++ .../com/markerhub/service/UserService.java | 16 +++ .../service/impl/BlogServiceImpl.java | 20 +++ .../service/impl/UserServiceImpl.java | 20 +++ .../com/markerhub/shiro/AccountProfile.java | 13 ++ .../com/markerhub/shiro/AccountRealm.java | 58 ++++++++ .../java/com/markerhub/shiro/JwtFilter.java | 103 +++++++++++++++ .../java/com/markerhub/shiro/JwtToken.java | 22 ++++ .../java/com/markerhub/util/JwtUtils.java | 62 +++++++++ .../com/markerhub/util/ValidationUtil.java | 124 ++++++++++++++++++ .../META-INF/spring-devtools.properties | 1 + .../src/main/resources/application.yml | 22 ++++ .../src/main/resources/mapper/BlogMapper.xml | 5 + .../src/main/resources/mapper/UserMapper.xml | 5 + ...tShiroMybatisplusDemoApplicationTests.java | 13 ++ 32 files changed, 1249 insertions(+) create mode 100644 README.md create mode 100644 vueblog-java/.gitignore create mode 100644 vueblog-java/pom.xml create mode 100644 vueblog-java/src/main/java/com/markerhub/Application.java create mode 100644 vueblog-java/src/main/java/com/markerhub/CodeGenerator.java create mode 100644 vueblog-java/src/main/java/com/markerhub/common/dto/LoginDto.java create mode 100644 vueblog-java/src/main/java/com/markerhub/common/exception/GlobalExcepitonHandler.java create mode 100644 vueblog-java/src/main/java/com/markerhub/common/lang/Result.java create mode 100644 vueblog-java/src/main/java/com/markerhub/config/CorsConfig.java create mode 100644 vueblog-java/src/main/java/com/markerhub/config/MybatisPlusConfig.java create mode 100644 vueblog-java/src/main/java/com/markerhub/config/ShiroConfig.java create mode 100644 vueblog-java/src/main/java/com/markerhub/controller/AccountController.java create mode 100644 vueblog-java/src/main/java/com/markerhub/controller/BlogController.java create mode 100644 vueblog-java/src/main/java/com/markerhub/entity/Blog.java create mode 100644 vueblog-java/src/main/java/com/markerhub/entity/User.java create mode 100644 vueblog-java/src/main/java/com/markerhub/mapper/BlogMapper.java create mode 100644 vueblog-java/src/main/java/com/markerhub/mapper/UserMapper.java create mode 100644 vueblog-java/src/main/java/com/markerhub/service/BlogService.java create mode 100644 vueblog-java/src/main/java/com/markerhub/service/UserService.java create mode 100644 vueblog-java/src/main/java/com/markerhub/service/impl/BlogServiceImpl.java create mode 100644 vueblog-java/src/main/java/com/markerhub/service/impl/UserServiceImpl.java create mode 100644 vueblog-java/src/main/java/com/markerhub/shiro/AccountProfile.java create mode 100644 vueblog-java/src/main/java/com/markerhub/shiro/AccountRealm.java create mode 100644 vueblog-java/src/main/java/com/markerhub/shiro/JwtFilter.java create mode 100644 vueblog-java/src/main/java/com/markerhub/shiro/JwtToken.java create mode 100644 vueblog-java/src/main/java/com/markerhub/util/JwtUtils.java create mode 100644 vueblog-java/src/main/java/com/markerhub/util/ValidationUtil.java create mode 100644 vueblog-java/src/main/resources/META-INF/spring-devtools.properties create mode 100644 vueblog-java/src/main/resources/application.yml create mode 100644 vueblog-java/src/main/resources/mapper/BlogMapper.xml create mode 100644 vueblog-java/src/main/resources/mapper/UserMapper.xml create mode 100644 vueblog-java/src/test/java/com/markerhub/SpringbootShiroMybatisplusDemoApplicationTests.java diff --git a/README.md b/README.md new file mode 100644 index 0000000..0198165 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +公众号:MarkerHub \ No newline at end of file diff --git a/vueblog-java/.gitignore b/vueblog-java/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/vueblog-java/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/vueblog-java/pom.xml b/vueblog-java/pom.xml new file mode 100644 index 0000000..16e8245 --- /dev/null +++ b/vueblog-java/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + + com.markerhub + vblog-java + 0.0.1-SNAPSHOT + vblog-java + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + + + com.baomidou + mybatis-plus-boot-starter + 3.2.0 + + + mysql + mysql-connector-java + runtime + + + + + com.baomidou + mybatis-plus-generator + 3.2.0 + + + + + org.crazycake + shiro-redis-spring-boot-starter + 3.2.1 + + + + + cn.hutool + hutool-all + 5.3.3 + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + org.springframework.boot + spring-boot-starter-freemarker + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/vueblog-java/src/main/java/com/markerhub/Application.java b/vueblog-java/src/main/java/com/markerhub/Application.java new file mode 100644 index 0000000..eebaa9f --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/Application.java @@ -0,0 +1,14 @@ +package com.markerhub; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + System.out.println("http://localhost:8080"); + } + +} diff --git a/vueblog-java/src/main/java/com/markerhub/CodeGenerator.java b/vueblog-java/src/main/java/com/markerhub/CodeGenerator.java new file mode 100644 index 0000000..35d7a50 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/CodeGenerator.java @@ -0,0 +1,116 @@ +package com.markerhub; + +import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.generator.AutoGenerator; +import com.baomidou.mybatisplus.generator.InjectionConfig; +import com.baomidou.mybatisplus.generator.config.*; +import com.baomidou.mybatisplus.generator.config.po.TableInfo; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中 +public class CodeGenerator { + + /** + *

+ * 读取控制台内容 + *

+ */ + public static String scanner(String tip) { + Scanner scanner = new Scanner(System.in); + StringBuilder help = new StringBuilder(); + help.append("请输入" + tip + ":"); + System.out.println(help.toString()); + if (scanner.hasNext()) { + String ipt = scanner.next(); + if (StringUtils.isNotEmpty(ipt)) { + return ipt; + } + } + throw new MybatisPlusException("请输入正确的" + tip + "!"); + } + + public static void main(String[] args) { + // 代码生成器 + AutoGenerator mpg = new AutoGenerator(); + + // 全局配置 + GlobalConfig gc = new GlobalConfig(); + String projectPath = System.getProperty("user.dir"); + gc.setOutputDir(projectPath + "/src/main/java"); +// gc.setOutputDir("D:\\test"); + gc.setAuthor("关注公众号:MarkerHub"); + gc.setOpen(false); + // gc.setSwagger2(true); 实体属性 Swagger2 注解 + gc.setServiceName("%sService"); + mpg.setGlobalConfig(gc); + + // 数据源配置 + DataSourceConfig dsc = new DataSourceConfig(); + dsc.setUrl("jdbc:mysql://localhost:3306/markerhub?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC"); + // dsc.setSchemaName("public"); + dsc.setDriverName("com.mysql.cj.jdbc.Driver"); + dsc.setUsername("root"); + dsc.setPassword("admin"); + mpg.setDataSource(dsc); + + // 包配置 + PackageConfig pc = new PackageConfig(); + pc.setModuleName(null); + pc.setParent("com.markerhub"); + mpg.setPackageInfo(pc); + + // 自定义配置 + InjectionConfig cfg = new InjectionConfig() { + @Override + public void initMap() { + // to do nothing + } + }; + + // 如果模板引擎是 freemarker + String templatePath = "/templates/mapper.xml.ftl"; + // 如果模板引擎是 velocity + // String templatePath = "/templates/mapper.xml.vm"; + + // 自定义输出配置 + List focList = new ArrayList<>(); + // 自定义配置会被优先输出 + focList.add(new FileOutConfig(templatePath) { + @Override + public String outputFile(TableInfo tableInfo) { + // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! + return projectPath + "/src/main/resources/mapper/" + + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; + } + }); + + cfg.setFileOutConfigList(focList); + mpg.setCfg(cfg); + + // 配置模板 + TemplateConfig templateConfig = new TemplateConfig(); + + templateConfig.setXml(null); + mpg.setTemplate(templateConfig); + + // 策略配置 + StrategyConfig strategy = new StrategyConfig(); + strategy.setNaming(NamingStrategy.underline_to_camel); + strategy.setColumnNaming(NamingStrategy.underline_to_camel); + strategy.setEntityLombokModel(true); + strategy.setRestControllerStyle(true); + strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); + strategy.setControllerMappingHyphenStyle(true); + strategy.setTablePrefix("m_"); + mpg.setStrategy(strategy); + mpg.setTemplateEngine(new FreemarkerTemplateEngine()); + mpg.execute(); + } +} \ No newline at end of file diff --git a/vueblog-java/src/main/java/com/markerhub/common/dto/LoginDto.java b/vueblog-java/src/main/java/com/markerhub/common/dto/LoginDto.java new file mode 100644 index 0000000..024de3b --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/common/dto/LoginDto.java @@ -0,0 +1,17 @@ +package com.markerhub.common.dto; + +import com.markerhub.entity.User; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +@Data +public class LoginDto implements Serializable { + + @NotBlank(message = "昵称不能为空") + private String username; + + @NotBlank(message = "密码不能为空") + private String password; +} diff --git a/vueblog-java/src/main/java/com/markerhub/common/exception/GlobalExcepitonHandler.java b/vueblog-java/src/main/java/com/markerhub/common/exception/GlobalExcepitonHandler.java new file mode 100644 index 0000000..cc2d38e --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/common/exception/GlobalExcepitonHandler.java @@ -0,0 +1,61 @@ +package com.markerhub.common.exception; + +import com.markerhub.common.lang.Result; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.ShiroException; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.io.IOException; + +/** + * 全局异常处理 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExcepitonHandler { + + // 捕捉shiro的异常 + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ExceptionHandler(ShiroException.class) + public Result handle401(ShiroException e) { + return Result.fail(401, e.getMessage(), null); + } + + /** + * 处理Assert的异常 + */ + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(value = IllegalArgumentException.class) + public Result handler(IllegalArgumentException e) throws IOException { + log.error("Assert异常:-------------->{}",e.getMessage()); + return Result.fail(e.getMessage()); + } + + /** + * @Validated 校验错误异常处理 + */ + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(value = MethodArgumentNotValidException.class) + public Result handler(MethodArgumentNotValidException e) throws IOException { + log.error("运行时异常:-------------->",e); + + BindingResult bindingResult = e.getBindingResult(); + ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get(); + return Result.fail(objectError.getDefaultMessage()); + } + + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(value = RuntimeException.class) + public Result handler(RuntimeException e) throws IOException { + log.error("运行时异常:-------------->",e); + return Result.fail(e.getMessage()); + } + +} diff --git a/vueblog-java/src/main/java/com/markerhub/common/lang/Result.java b/vueblog-java/src/main/java/com/markerhub/common/lang/Result.java new file mode 100644 index 0000000..978a311 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/common/lang/Result.java @@ -0,0 +1,42 @@ +package com.markerhub.common.lang; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class Result implements Serializable { + + private int code; + private String msg; + private Object data; + + public static Result succ(Object data) { + return succ("操作成功", data); + } + + public static Result succ(String mess, Object data) { + Result m = new Result(); + m.setCode(200); + m.setData(data); + m.setMsg(mess); + return m; + } + + public static Result fail(String mess) { + return fail(mess, null); + } + + public static Result fail(String mess, Object data) { + return fail(400, mess, data); + } + + public static Result fail(int code, String mess, Object data) { + Result m = new Result(); + m.setCode(code); + m.setData(data); + m.setMsg(mess); + return m; + } + +} \ No newline at end of file diff --git a/vueblog-java/src/main/java/com/markerhub/config/CorsConfig.java b/vueblog-java/src/main/java/com/markerhub/config/CorsConfig.java new file mode 100644 index 0000000..500b48f --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/config/CorsConfig.java @@ -0,0 +1,19 @@ +package com.markerhub.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") + .allowCredentials(true) + .maxAge(3600) + .allowedHeaders("*"); + } +} \ No newline at end of file diff --git a/vueblog-java/src/main/java/com/markerhub/config/MybatisPlusConfig.java b/vueblog-java/src/main/java/com/markerhub/config/MybatisPlusConfig.java new file mode 100644 index 0000000..b0cd5a8 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/config/MybatisPlusConfig.java @@ -0,0 +1,19 @@ +package com.markerhub.config; + +import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +@MapperScan("com.markerhub.mapper") +public class MybatisPlusConfig { + + @Bean + public PaginationInterceptor paginationInterceptor() { + PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); + return paginationInterceptor; + } +} \ No newline at end of file diff --git a/vueblog-java/src/main/java/com/markerhub/config/ShiroConfig.java b/vueblog-java/src/main/java/com/markerhub/config/ShiroConfig.java new file mode 100644 index 0000000..e6368b6 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/config/ShiroConfig.java @@ -0,0 +1,110 @@ +package com.markerhub.config; + +import com.markerhub.shiro.AccountRealm; +import com.markerhub.shiro.JwtFilter; +import io.jsonwebtoken.Jwt; +import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; +import org.apache.shiro.mgt.DefaultSubjectDAO; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; +import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.crazycake.shiro.RedisCacheManager; +import org.crazycake.shiro.RedisSessionDAO; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class ShiroConfig { + + @Autowired + JwtFilter jwtFilter; + + @Bean + public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) { + DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); + sessionManager.setSessionDAO(redisSessionDAO); + return sessionManager; + } + + @Bean + public DefaultWebSecurityManager securityManager(AccountRealm accountRealm, + SessionManager sessionManager, + RedisCacheManager redisCacheManager) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm); + + securityManager.setSessionManager(sessionManager); + securityManager.setCacheManager(redisCacheManager); + + /* + * 关闭shiro自带的session,详情见文档 + * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29 + */ + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); + DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + defaultSessionStorageEvaluator.setSessionStorageEnabled(false); + subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); + securityManager.setSubjectDAO(subjectDAO); + + return securityManager; + } + + @Bean + public ShiroFilterChainDefinition shiroFilterChainDefinition() { + DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); + + Map filterMap = new LinkedHashMap<>(); +// filterMap.put("/webjars/**", "anon"); +// filterMap.put("/login", "anon"); +// filterMap.put("/swagger/**", "anon"); +// filterMap.put("/captcha.jpg", "anon"); + + filterMap.put("/**", "jwt"); // 主要通过注解方式校验权限 + chainDefinition.addPathDefinitions(filterMap); + return chainDefinition; + } + + @Bean("shiroFilterFactoryBean") + public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, + ShiroFilterChainDefinition shiroFilterChainDefinition) { + ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); + shiroFilter.setSecurityManager(securityManager); + + Map filters = new HashMap<>(); + filters.put("jwt", jwtFilter); + shiroFilter.setFilters(filters); + + Map filterMap = shiroFilterChainDefinition.getFilterChainMap(); + + shiroFilter.setFilterChainDefinitionMap(filterMap); + return shiroFilter; + } + + + // 注解代理 + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ + AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); + return authorizationAttributeSourceAdvisor; + } + + @Bean + public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); + return creator; + } +} diff --git a/vueblog-java/src/main/java/com/markerhub/controller/AccountController.java b/vueblog-java/src/main/java/com/markerhub/controller/AccountController.java new file mode 100644 index 0000000..fcd6dfe --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/controller/AccountController.java @@ -0,0 +1,55 @@ +package com.markerhub.controller; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.crypto.SecureUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.markerhub.common.dto.LoginDto; +import com.markerhub.common.lang.Result; +import com.markerhub.entity.User; +import com.markerhub.service.UserService; +import com.markerhub.util.JwtUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@RestController +public class AccountController { + + @Autowired + JwtUtils jwtUtils; + + @Autowired + UserService userService; + + @CrossOrigin + @PostMapping("/login") + public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response) { + + User user = userService.getOne(new QueryWrapper().eq("username", loginDto.getUsername())); + Assert.notNull(user, "用户不存在"); + + if(!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))) { + return Result.fail("密码错误!"); + } + + String jwt = jwtUtils.generateToken(user.getId()); + + response.setHeader("Authorization", jwt); + response.setHeader("Access-Control-Expose-Headers", "Authorization"); + + return Result.succ(MapUtil.builder() + .put("id", user.getId()) + .put("username", user.getUsername()) + .put("avatar", user.getAvatar()) + .put("email", user.getEmail()) + ); + } + +} diff --git a/vueblog-java/src/main/java/com/markerhub/controller/BlogController.java b/vueblog-java/src/main/java/com/markerhub/controller/BlogController.java new file mode 100644 index 0000000..c563ac6 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/controller/BlogController.java @@ -0,0 +1,34 @@ +package com.markerhub.controller; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.markerhub.common.lang.Result; +import com.markerhub.service.BlogService; +import org.apache.shiro.authz.annotation.RequiresAuthentication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +public class BlogController { + + @Autowired + BlogService blogService; + + @GetMapping("/blogs") + @RequiresAuthentication + public Result blogs(Integer currentPage) { + + if(currentPage == null || currentPage < 1) currentPage = 1; + + Page page = new Page(currentPage, 10); + IPage pageData = blogService.page(page); + + return Result.succ(pageData); + } + +} diff --git a/vueblog-java/src/main/java/com/markerhub/entity/Blog.java b/vueblog-java/src/main/java/com/markerhub/entity/Blog.java new file mode 100644 index 0000000..8eb1a4e --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/entity/Blog.java @@ -0,0 +1,42 @@ +package com.markerhub.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + *

+ * + *

+ * + * @author 关注公众号:MarkerHub + * @since 2020-05-14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("m_blog") +public class Blog implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private String title; + + private String summary; + + private String content; + + private LocalDateTime created; + + private Integer status; + + +} diff --git a/vueblog-java/src/main/java/com/markerhub/entity/User.java b/vueblog-java/src/main/java/com/markerhub/entity/User.java new file mode 100644 index 0000000..7e9a80c --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/entity/User.java @@ -0,0 +1,49 @@ +package com.markerhub.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; + +/** + * + * @author 关注公众号:MarkerHub + * @since 2020-04-20 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("m_user") +public class User implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @NotBlank(message = "昵称不能为空") + private String username; + + private String avatar; + + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + private String email; + + private String password; + + private Integer status; + + private LocalDateTime created; + + private LocalDateTime lastLogin; + + +} diff --git a/vueblog-java/src/main/java/com/markerhub/mapper/BlogMapper.java b/vueblog-java/src/main/java/com/markerhub/mapper/BlogMapper.java new file mode 100644 index 0000000..5bc571b --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/mapper/BlogMapper.java @@ -0,0 +1,16 @@ +package com.markerhub.mapper; + +import com.markerhub.entity.Blog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author 关注公众号:MarkerHub + * @since 2020-05-14 + */ +public interface BlogMapper extends BaseMapper { + +} diff --git a/vueblog-java/src/main/java/com/markerhub/mapper/UserMapper.java b/vueblog-java/src/main/java/com/markerhub/mapper/UserMapper.java new file mode 100644 index 0000000..5a35aa6 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/mapper/UserMapper.java @@ -0,0 +1,16 @@ +package com.markerhub.mapper; + +import com.markerhub.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author 关注公众号:MarkerHub + * @since 2020-04-20 + */ +public interface UserMapper extends BaseMapper { + +} diff --git a/vueblog-java/src/main/java/com/markerhub/service/BlogService.java b/vueblog-java/src/main/java/com/markerhub/service/BlogService.java new file mode 100644 index 0000000..0656e54 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/service/BlogService.java @@ -0,0 +1,16 @@ +package com.markerhub.service; + +import com.markerhub.entity.Blog; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author 关注公众号:MarkerHub + * @since 2020-05-14 + */ +public interface BlogService extends IService { + +} diff --git a/vueblog-java/src/main/java/com/markerhub/service/UserService.java b/vueblog-java/src/main/java/com/markerhub/service/UserService.java new file mode 100644 index 0000000..0b75508 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/service/UserService.java @@ -0,0 +1,16 @@ +package com.markerhub.service; + +import com.markerhub.entity.User; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author 关注公众号:MarkerHub + * @since 2020-04-20 + */ +public interface UserService extends IService { + +} diff --git a/vueblog-java/src/main/java/com/markerhub/service/impl/BlogServiceImpl.java b/vueblog-java/src/main/java/com/markerhub/service/impl/BlogServiceImpl.java new file mode 100644 index 0000000..56fd516 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/service/impl/BlogServiceImpl.java @@ -0,0 +1,20 @@ +package com.markerhub.service.impl; + +import com.markerhub.entity.Blog; +import com.markerhub.mapper.BlogMapper; +import com.markerhub.service.BlogService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 服务实现类 + *

+ * + * @author 关注公众号:MarkerHub + * @since 2020-05-14 + */ +@Service +public class BlogServiceImpl extends ServiceImpl implements BlogService { + +} diff --git a/vueblog-java/src/main/java/com/markerhub/service/impl/UserServiceImpl.java b/vueblog-java/src/main/java/com/markerhub/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..666c03f --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/service/impl/UserServiceImpl.java @@ -0,0 +1,20 @@ +package com.markerhub.service.impl; + +import com.markerhub.entity.User; +import com.markerhub.mapper.UserMapper; +import com.markerhub.service.UserService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 服务实现类 + *

+ * + * @author 关注公众号:MarkerHub + * @since 2020-04-20 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + +} diff --git a/vueblog-java/src/main/java/com/markerhub/shiro/AccountProfile.java b/vueblog-java/src/main/java/com/markerhub/shiro/AccountProfile.java new file mode 100644 index 0000000..6cbde84 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/shiro/AccountProfile.java @@ -0,0 +1,13 @@ +package com.markerhub.shiro; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class AccountProfile implements Serializable { + + private Long id; + private String username; + +} diff --git a/vueblog-java/src/main/java/com/markerhub/shiro/AccountRealm.java b/vueblog-java/src/main/java/com/markerhub/shiro/AccountRealm.java new file mode 100644 index 0000000..64feacf --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/shiro/AccountRealm.java @@ -0,0 +1,58 @@ +package com.markerhub.shiro; + +import cn.hutool.core.bean.BeanUtil; +import com.markerhub.entity.User; +import com.markerhub.service.UserService; +import com.markerhub.util.JwtUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.*; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class AccountRealm extends AuthorizingRealm { + + @Autowired + JwtUtils jwtUtils; + + @Autowired + UserService userService; + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof JwtToken; + } + + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + return null; + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + JwtToken jwt = (JwtToken) token; + + log.info("jwt----------------->{}", jwt); + + String userId = jwtUtils.getClaimByToken((String) jwt.getPrincipal()).getSubject(); + + User user = userService.getById(Long.parseLong(userId)); + if(user == null) { + throw new UnknownAccountException("账户不存在!"); + } + if(user.getStatus() == -1) { + throw new LockedAccountException("账户已被锁定!"); + } + + AccountProfile profile = new AccountProfile(); + BeanUtil.copyProperties(user, profile); + + log.info("profile----------------->{}", profile.toString()); + + return new SimpleAuthenticationInfo(profile, jwt.getCredentials(), getName()); + } +} diff --git a/vueblog-java/src/main/java/com/markerhub/shiro/JwtFilter.java b/vueblog-java/src/main/java/com/markerhub/shiro/JwtFilter.java new file mode 100644 index 0000000..7915fa1 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/shiro/JwtFilter.java @@ -0,0 +1,103 @@ +package com.markerhub.shiro; + +import cn.hutool.http.HttpStatus; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.extension.api.R; +import com.markerhub.common.lang.Result; +import com.markerhub.util.JwtUtils; +import io.jsonwebtoken.Claims; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.ExpiredCredentialsException; +import org.apache.shiro.web.filter.authc.AuthenticatingFilter; +import org.apache.shiro.web.util.WebUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtFilter extends AuthenticatingFilter { + + @Autowired + JwtUtils jwtUtils; + + @Override + protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { + // 获取 token + HttpServletRequest request = (HttpServletRequest) servletRequest; + String jwt = request.getHeader("Authorization"); + + if(StringUtils.isEmpty(jwt)){ + return null; + } + + return new JwtToken(jwt); + } + + @Override + protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { + + HttpServletRequest request = (HttpServletRequest) servletRequest; + String token = request.getHeader("Authorization"); + + if(StringUtils.isEmpty(token)) { +// HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; +// +// String json = JSONUtil.toJsonStr(Result.fail(HttpStatus.HTTP_UNAUTHORIZED, "invalid token", null)); +// httpResponse.getWriter().print(json); + return true; + } else { + + // 判断是否已过期 + Claims claim = jwtUtils.getClaimByToken(token); + if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) { + throw new ExpiredCredentialsException("token已失效,请重新登录!"); + } + } + + // 执行自动登录 + return executeLogin(servletRequest, servletResponse); + } + + @Override + protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { + HttpServletResponse httpResponse = (HttpServletResponse) response; + try { + //处理登录失败的异常 + Throwable throwable = e.getCause() == null ? e : e.getCause(); + Result r = Result.fail(throwable.getMessage()); + + String json = JSONUtil.toJsonStr(r); + httpResponse.getWriter().print(json); + } catch (IOException e1) { + + } + + return false; + } + + /** + * 对跨域提供支持 + */ + @Override + protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { + HttpServletRequest httpServletRequest = WebUtils.toHttp(request); + HttpServletResponse httpServletResponse = WebUtils.toHttp(response); + httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); + httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); + httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); + // 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态 + if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { + httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value()); + return false; + } + return super.preHandle(request, response); + } +} diff --git a/vueblog-java/src/main/java/com/markerhub/shiro/JwtToken.java b/vueblog-java/src/main/java/com/markerhub/shiro/JwtToken.java new file mode 100644 index 0000000..6917e1e --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/shiro/JwtToken.java @@ -0,0 +1,22 @@ +package com.markerhub.shiro; + +import org.apache.shiro.authc.AuthenticationToken; + +public class JwtToken implements AuthenticationToken { + + private String token; + + public JwtToken(String token) { + this.token = token; + } + + @Override + public Object getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} diff --git a/vueblog-java/src/main/java/com/markerhub/util/JwtUtils.java b/vueblog-java/src/main/java/com/markerhub/util/JwtUtils.java new file mode 100644 index 0000000..5e3ae6a --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/util/JwtUtils.java @@ -0,0 +1,62 @@ +package com.markerhub.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * jwt工具类 + */ +@Slf4j +@Data +@Component +@ConfigurationProperties(prefix = "markerhub.jwt") +public class JwtUtils { + + private String secret; + private long expire; + private String header; + + /** + * 生成jwt token + */ + public String generateToken(long userId) { + Date nowDate = new Date(); + //过期时间 + Date expireDate = new Date(nowDate.getTime() + expire * 1000); + + return Jwts.builder() + .setHeaderParam("typ", "JWT") + .setSubject(userId+"") + .setIssuedAt(nowDate) + .setExpiration(expireDate) + .signWith(SignatureAlgorithm.HS512, secret) + .compact(); + } + + public Claims getClaimByToken(String token) { + try { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + }catch (Exception e){ + log.debug("validate is token error ", e); + return null; + } + } + + /** + * token是否过期 + * @return true:过期 + */ + public boolean isTokenExpired(Date expiration) { + return expiration.before(new Date()); + } +} diff --git a/vueblog-java/src/main/java/com/markerhub/util/ValidationUtil.java b/vueblog-java/src/main/java/com/markerhub/util/ValidationUtil.java new file mode 100644 index 0000000..e942373 --- /dev/null +++ b/vueblog-java/src/main/java/com/markerhub/util/ValidationUtil.java @@ -0,0 +1,124 @@ +package com.markerhub.util; + +import lombok.Data; +import org.hibernate.validator.HibernateValidator; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class ValidationUtil { + + /** + * 开启快速结束模式 failFast (true) + */ + private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator(); + /** + * 校验对象 + * + * @param t bean + * @param groups 校验组 + * @return ValidResult + */ + public static ValidResult validateBean(T t,Class...groups) { + ValidResult result = new ValidationUtil().new ValidResult(); + Set> violationSet = validator.validate(t,groups); + boolean hasError = violationSet != null && violationSet.size() > 0; + result.setHasErrors(hasError); + if (hasError) { + for (ConstraintViolation violation : violationSet) { + result.addError(violation.getPropertyPath().toString(), violation.getMessage()); + } + } + return result; + } + /** + * 校验bean的某一个属性 + * + * @param obj bean + * @param propertyName 属性名称 + * @return ValidResult + */ + public static ValidResult validateProperty(T obj, String propertyName) { + ValidResult result = new ValidationUtil().new ValidResult(); + Set> violationSet = validator.validateProperty(obj, propertyName); + boolean hasError = violationSet != null && violationSet.size() > 0; + result.setHasErrors(hasError); + if (hasError) { + for (ConstraintViolation violation : violationSet) { + result.addError(propertyName, violation.getMessage()); + } + } + return result; + } + /** + * 校验结果类 + */ + @Data + public class ValidResult { + + /** + * 是否有错误 + */ + private boolean hasErrors; + + /** + * 错误信息 + */ + private List errors; + + public ValidResult() { + this.errors = new ArrayList<>(); + } + public boolean hasErrors() { + return hasErrors; + } + + public void setHasErrors(boolean hasErrors) { + this.hasErrors = hasErrors; + } + + /** + * 获取所有验证信息 + * @return 集合形式 + */ + public List getAllErrors() { + return errors; + } + /** + * 获取所有验证信息 + * @return 字符串形式 + */ + public String getErrors(){ + StringBuilder sb = new StringBuilder(); + for (ErrorMessage error : errors) { + sb.append(error.getPropertyPath()).append(":").append(error.getMessage()).append(" "); + } + return sb.toString(); + } + + public void addError(String propertyName, String message) { + this.errors.add(new ErrorMessage(propertyName, message)); + } + } + + @Data + public class ErrorMessage { + + private String propertyPath; + + private String message; + + public ErrorMessage() { + } + + public ErrorMessage(String propertyPath, String message) { + this.propertyPath = propertyPath; + this.message = message; + } + } + +} \ No newline at end of file diff --git a/vueblog-java/src/main/resources/META-INF/spring-devtools.properties b/vueblog-java/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..1cd4a1b --- /dev/null +++ b/vueblog-java/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.shiro-redis=/shiro-[\\w-\\.]+jar \ No newline at end of file diff --git a/vueblog-java/src/main/resources/application.yml b/vueblog-java/src/main/resources/application.yml new file mode 100644 index 0000000..4c94479 --- /dev/null +++ b/vueblog-java/src/main/resources/application.yml @@ -0,0 +1,22 @@ +server: + port: 8081 +# DataSource Config +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/markerhub?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC + username: root + password: admin +mybatis-plus: + mapper-locations: classpath*:/mapper/**Mapper.xml +shiro-redis: + enabled: true + redis-manager: + host: 127.0.0.1:6379 +markerhub: + jwt: + # 加密秘钥 + secret: f4e2e52034348f86b67cde581c0f9eb5 + # token有效时长,7天,单位秒 + expire: 604800 + header: token diff --git a/vueblog-java/src/main/resources/mapper/BlogMapper.xml b/vueblog-java/src/main/resources/mapper/BlogMapper.xml new file mode 100644 index 0000000..4b610b7 --- /dev/null +++ b/vueblog-java/src/main/resources/mapper/BlogMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vueblog-java/src/main/resources/mapper/UserMapper.xml b/vueblog-java/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..8df9f9b --- /dev/null +++ b/vueblog-java/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vueblog-java/src/test/java/com/markerhub/SpringbootShiroMybatisplusDemoApplicationTests.java b/vueblog-java/src/test/java/com/markerhub/SpringbootShiroMybatisplusDemoApplicationTests.java new file mode 100644 index 0000000..34a74a4 --- /dev/null +++ b/vueblog-java/src/test/java/com/markerhub/SpringbootShiroMybatisplusDemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.markerhub; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringbootShiroMybatisplusDemoApplicationTests { + + @Test + void contextLoads() { + } + +}