├── .gitignore ├── README.md ├── flow.jpg ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── springboot │ │ │ ├── SpringbootApplication.java │ │ │ ├── config │ │ │ └── Config.java │ │ │ ├── controller │ │ │ ├── ApiController.java │ │ │ └── IndexController.java │ │ │ ├── dao │ │ │ ├── BaseDao.java │ │ │ ├── DnPromotionInfoDao.java │ │ │ ├── SysMenuDao.java │ │ │ ├── SysUserDao.java │ │ │ └── impl │ │ │ │ └── BaseDaoImpl.java │ │ │ ├── dto │ │ │ ├── ApiFuncData.java │ │ │ └── Result.java │ │ │ ├── entity │ │ │ ├── DnPromotionInfoEntity.java │ │ │ ├── SysMenuEntity.java │ │ │ ├── SysPermissionEntity.java │ │ │ ├── SysRoleEntity.java │ │ │ ├── SysRolePermissionEntity.java │ │ │ ├── SysUserEntity.java │ │ │ └── SysUserRoleEntity.java │ │ │ ├── gentry │ │ │ ├── DnChannel.java │ │ │ ├── DnChannelMapper.java │ │ │ ├── DnChannelMapper.xml │ │ │ ├── DnPromotionInfo.java │ │ │ ├── DnPromotionInfoMapper.java │ │ │ └── DnPromotionInfoMapper.xml │ │ │ ├── mybatis │ │ │ ├── PageHelper.java │ │ │ └── PageInterceptor.java │ │ │ ├── service │ │ │ ├── ApiService.java │ │ │ └── UserService.java │ │ │ ├── shiro │ │ │ ├── MyExceptionHandler.java │ │ │ ├── MyRedisSessionDAO.java │ │ │ ├── MySessionManager.java │ │ │ ├── MyShiroRealm.java │ │ │ └── ShiroConfig.java │ │ │ └── util │ │ │ ├── JSONUtil.java │ │ │ ├── RedisCacheManager.java │ │ │ ├── SpringContextHolder.java │ │ │ └── ValidateUtil.java │ └── resources │ │ ├── application.yml │ │ ├── mybatis-generator │ │ ├── generatorConfig.xml │ │ └── mybatisGeneratorinit.properties │ │ └── static │ │ └── favicon.ico ├── test │ └── java │ │ └── com │ │ └── example │ │ └── springboot │ │ └── SpringbootApplicationTests.java └── vue-admin-template │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .postcssrc.js │ ├── .travis.yml │ ├── LICENSE │ ├── README-zh.md │ ├── README.md │ ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js │ ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js │ ├── favicon.ico │ ├── index.html │ ├── package.json │ ├── src │ ├── App.vue │ ├── api │ │ ├── login.js │ │ └── table.js │ ├── assets │ │ └── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ ├── components │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ ├── Hamburger │ │ │ └── index.vue │ │ └── SvgIcon │ │ │ └── index.vue │ ├── icons │ │ ├── index.js │ │ ├── svg │ │ │ ├── example.svg │ │ │ ├── eye.svg │ │ │ ├── form.svg │ │ │ ├── link.svg │ │ │ ├── nested.svg │ │ │ ├── password.svg │ │ │ ├── table.svg │ │ │ ├── tree.svg │ │ │ └── user.svg │ │ └── svgo.yml │ ├── main.js │ ├── permission.js │ ├── router │ │ └── index.js │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ ├── app.js │ │ │ └── user.js │ ├── styles │ │ ├── element-ui.scss │ │ ├── index.scss │ │ ├── mixin.scss │ │ ├── sidebar.scss │ │ ├── transition.scss │ │ └── variables.scss │ ├── utils │ │ ├── auth.js │ │ ├── index.js │ │ ├── request.js │ │ └── validate.js │ └── views │ │ ├── 404.vue │ │ ├── dashboard │ │ └── index.vue │ │ ├── form │ │ └── index.vue │ │ ├── layout │ │ ├── Layout.vue │ │ ├── components │ │ │ ├── AppMain.vue │ │ │ ├── Navbar.vue │ │ │ ├── Sidebar │ │ │ │ ├── Item.vue │ │ │ │ ├── Link.vue │ │ │ │ ├── SidebarItem.vue │ │ │ │ └── index.vue │ │ │ └── index.js │ │ └── mixin │ │ │ └── ResizeHandler.js │ │ ├── login │ │ └── index.vue │ │ ├── nested │ │ ├── menu1 │ │ │ ├── index.vue │ │ │ ├── menu1-1 │ │ │ │ └── index.vue │ │ │ ├── menu1-2 │ │ │ │ ├── index.vue │ │ │ │ ├── menu1-2-1 │ │ │ │ │ └── index.vue │ │ │ │ └── menu1-2-2 │ │ │ │ │ └── index.vue │ │ │ └── menu1-3 │ │ │ │ └── index.vue │ │ └── menu2 │ │ │ └── index.vue │ │ ├── table │ │ └── index.vue │ │ └── tree │ │ └── index.vue │ └── static │ └── .gitkeep └── test.sql /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | ### Node template 27 | # Logs 28 | logs 29 | *.log 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | 34 | # Runtime data 35 | pids 36 | *.pid 37 | *.seed 38 | *.pid.lock 39 | 40 | # Directory for instrumented libs generated by jscoverage/JSCover 41 | lib-cov 42 | 43 | # Coverage directory used by tools like istanbul 44 | coverage 45 | 46 | # nyc test coverage 47 | .nyc_output 48 | 49 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 50 | .grunt 51 | 52 | # Bower dependency directory (https://bower.io/) 53 | bower_components 54 | 55 | # node-waf configuration 56 | .lock-wscript 57 | 58 | # Compiled binary addons (https://nodejs.org/api/addons.html) 59 | build/Release 60 | 61 | # Dependency directories 62 | node_modules/ 63 | jspm_packages/ 64 | 65 | # TypeScript v1 declaration files 66 | typings/ 67 | 68 | # Optional npm cache directory 69 | .npm 70 | 71 | # Optional eslint cache 72 | .eslintcache 73 | 74 | # Optional REPL history 75 | .node_repl_history 76 | 77 | # Output of 'npm pack' 78 | *.tgz 79 | 80 | # Yarn Integrity file 81 | .yarn-integrity 82 | 83 | # dotenv environment variables file 84 | .env 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | 89 | # next.js build output 90 | .next 91 | 92 | # nuxt.js build output 93 | .nuxt 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless 100 | ### JetBrains template 101 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 102 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 103 | 104 | # User-specific stuff 105 | .idea/**/workspace.xml 106 | .idea/**/tasks.xml 107 | .idea/**/usage.statistics.xml 108 | .idea/**/dictionaries 109 | .idea/**/shelf 110 | 111 | # Sensitive or high-churn files 112 | .idea/**/dataSources/ 113 | .idea/**/dataSources.ids 114 | .idea/**/dataSources.local.xml 115 | .idea/**/sqlDataSources.xml 116 | .idea/**/dynamic.xml 117 | .idea/**/uiDesigner.xml 118 | .idea/**/dbnavigator.xml 119 | 120 | # Gradle 121 | .idea/**/gradle.xml 122 | .idea/**/libraries 123 | 124 | # Gradle and Maven with auto-import 125 | # When using Gradle or Maven with auto-import, you should exclude module files, 126 | # since they will be recreated, and may cause churn. Uncomment if using 127 | # auto-import. 128 | # .idea/modules.xml 129 | # .idea/*.iml 130 | # .idea/modules 131 | 132 | # CMake 133 | cmake-build-*/ 134 | 135 | # Mongo Explorer plugin 136 | .idea/**/mongoSettings.xml 137 | 138 | # File-based project format 139 | *.iws 140 | 141 | # IntelliJ 142 | out/ 143 | 144 | # mpeltonen/sbt-idea plugin 145 | .idea_modules/ 146 | 147 | # JIRA plugin 148 | atlassian-ide-plugin.xml 149 | 150 | # Cursive Clojure plugin 151 | .idea/replstate.xml 152 | 153 | # Crashlytics plugin (for Android Studio and IntelliJ) 154 | com_crashlytics_export_strings.xml 155 | crashlytics.properties 156 | crashlytics-build.properties 157 | fabric.properties 158 | 159 | # Editor-based Rest Client 160 | .idea/httpRequests 161 | ### Java template 162 | # Compiled class file 163 | *.class 164 | 165 | # Log file 166 | *.log 167 | 168 | # BlueJ files 169 | *.ctxt 170 | 171 | # Mobile Tools for Java (J2ME) 172 | .mtj.tmp/ 173 | 174 | # Package Files # 175 | *.jar 176 | *.war 177 | *.nar 178 | *.ear 179 | *.zip 180 | *.tar.gz 181 | *.rar 182 | 183 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 184 | hs_err_pid* 185 | *.mvn/ 186 | mvnw 187 | mvnw.cmd 188 | .DS_Store 189 | src/main/resources/static/dist 190 | 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 所有源码可以在[github](https://github.com/fwing1987/SpringBoot-Shiro-Vue.git)上找到,文章参考: 2 | [提供一套基于Spring Boot-Shiro-Vue的权限管理思路.前后端都加以控制,做到按钮/接口级别的权限](https://github.com/Heeexy/SpringBoot-Shiro-Vue),做了自己的实现,练了下手。前文见:[前后端分离项目中权限控制的思考](https://blog.csdn.net/pur_e/article/details/83506888),以及实现过程中的瞎折腾:[SpringBoot+Shiro瞎折腾——不使用Shiro的Filter模式](https://blog.csdn.net/pur_e/article/details/83749624)。 3 | PS:前端代码使用的是:[vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) 4 | # 一、Shiro基本概念 5 | 参见前文:[Shiro基本概念](https://blog.csdn.net/pur_e/article/details/83344041),里面有些术语理解起来可能没那么直观,配合下面的实例代码,看看是如何使用的,可以辅助理解。 6 | 7 | 8 | # 二、WEB的典型用户验证流程 9 | 10 |  11 | 12 | 特意将Cookie与SessionManager单独出来,是因为这两个是可选的,可配的: 13 | * Cookie可以选择不使用,只将sessionId存放在页面的内存中,这时页面被关闭,sessionId也就丢失了,当然,服务端还存在一个类似野指针的session;也可以选择使用localStorage; 14 | * SessionManager,Shiro提供一个默认的DefaultWebSessionManager,是使用MemorySessionDAO在服务端的内存中保存session,但存在几个问题,不推荐在生产环境中使用: 15 | 1. 无法持久化,服务端重启下session就丢了; 16 | 2. 容易内存溢出; 17 | 3. 集群模式下无法session共享; 18 | 19 | # 三、实现 20 | 理一下思路,前后端分离权限验证与普通Web服务的权限验证有啥不同,其实就一个:```前后端变成了只交互数据```,展开来说,是后端少了两个对前端的控制: 21 | * ```页面路由``` 22 | * ```页面DOM``` 23 | 24 | 所以针对普通的web服务,需要做出如下修改: 25 | 1. 路由相关: 26 | * 未验证页面不能直接Shiro跳转,而是返回未登录错误,由前端实现 27 | ```java 28 | shiroFilterFactoryBean.setLoginUrl("/unauth"); 29 | @RequestMapping("/unauth") 30 | @ResponseBody 31 | public Result unauth(){ 32 | Result result = new Result(100); 33 | result.message = "未登录"; 34 | return result; 35 | } 36 | ``` 37 | * 配置所有URL为不需要登录验证,而由前端根据后端返回的权限数据来路由(别和不需要权限验证混了,只是因为路由不需要后端来做而已,所有需要权限验证的业务逻辑都需要有权限较验) 38 | ```java 39 | filterChainDefinitionMap.put("/", "anon"); 40 | ``` 41 | * 前端需要根据返回的权限数据进行路由配置,具体到Vue使用Vue router的addRoutes动态添加路由 42 | 43 | 2. DOM相关 44 | * 前端根据后端返回的权限数据决定菜单或按钮的展示 45 | 46 | 3. 跨域,这个是前后端分离带来的新问题(如果分开部署) 47 | * 后端需要配置允许跨域(添加@CrossOrigin或使用Filter) 48 | 49 | 没了,就需要修改这么多,具体实现就不贴大量代码了,源码见[github](https://github.com/fwing1987/SpringBoot-Shiro-Vue.git)。后续再尝试添加一下Redis的Session支持 50 | 51 | -------------------------------------------------------------------------------- /flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fwing1987/SpringBoot-Shiro-Vue/554b8392185c7ecb9ca56ba42da0ae0a30d32738/flow.jpg -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.example 7 | springboot 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | springboot 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.4.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 1.4.0 26 | 1.2.51 27 | 2.6 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-devtools 38 | true 39 | 40 | 41 | 42 | mysql 43 | mysql-connector-java 44 | runtime 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | true 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-data-jpa 60 | 61 | 62 | 63 | org.apache.commons 64 | commons-lang3 65 | 66 | 67 | 68 | commons-io 69 | commons-io 70 | ${common-io.version} 71 | 72 | 73 | 74 | com.alibaba 75 | fastjson 76 | ${fastjson.verson} 77 | 78 | 79 | org.apache.shiro 80 | shiro-spring 81 | ${shiro.version} 82 | 83 | 84 | org.springframework.boot 85 | spring-boot-starter-data-redis 86 | 87 | 88 | org.mybatis.spring.boot 89 | mybatis-spring-boot-starter 90 | 1.3.2 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | src/main/java 100 | 101 | **/*.java 102 | 103 | 104 | 105 | src/main/resources 106 | 107 | **/*.* 108 | 109 | 110 | 111 | 112 | 113 | org.springframework.boot 114 | spring-boot-maven-plugin 115 | 116 | 117 | true 118 | true 119 | 120 | 121 | 122 | org.mybatis.generator 123 | mybatis-generator-maven-plugin 124 | 1.3.2 125 | 126 | 127 | Generate MyBatis Artifacts 128 | deploy 129 | 130 | generate 131 | 132 | 133 | 134 | 135 | 136 | src/main/resources/mybatis-generator/generatorConfig.xml 137 | true 138 | true 139 | 140 | 141 | 142 | mysql 143 | mysql-connector-java 144 | 5.1.34 145 | 146 | 147 | org.mybatis.generator 148 | mybatis-generator-core 149 | 1.3.2 150 | 151 | 152 | 153 | 154 | org.springframework.boot 155 | spring-boot-maven-plugin 156 | 157 | exec 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/SpringbootApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot; 2 | 3 | import com.alibaba.fastjson.parser.Feature; 4 | import com.alibaba.fastjson.serializer.SerializerFeature; 5 | import com.alibaba.fastjson.support.config.FastJsonConfig; 6 | import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; 7 | import com.example.springboot.dao.impl.BaseDaoImpl; 8 | import org.mybatis.spring.annotation.MapperScan; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.boot.autoconfigure.http.HttpMessageConverters; 13 | import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 16 | import org.springframework.http.converter.HttpMessageConverter; 17 | import org.springframework.transaction.annotation.EnableTransactionManagement; 18 | 19 | @SpringBootApplication 20 | @EnableTransactionManagement 21 | @EnableJpaRepositories(repositoryBaseClass = BaseDaoImpl.class) 22 | @MapperScan("com.example.springboot.gentry") 23 | public class SpringbootApplication { 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(SpringbootApplication.class, args); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.config; 2 | 3 | import com.example.springboot.dto.ApiFuncData; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by apple on 2018/4/26. 12 | */ 13 | public class Config { 14 | //页面api接口 start 15 | public static Map API_FUNC_MAP = new HashMap(); 16 | 17 | static{ 18 | putApiFunc("apiService","signin","signin"); 19 | putApiFunc("apiService","logout","logout"); 20 | putApiFunc("apiService","getUserInfo","getUserInfo"); 21 | } 22 | 23 | private static void putApiFunc(String beanName,String method,String cmd){ 24 | putApiFunc(beanName,method,cmd,false); 25 | } 26 | 27 | private static void putApiFunc(String beanName,String method,String cmd,boolean isNeedSign){ 28 | ApiFuncData api = new ApiFuncData(); 29 | api.beanName = beanName; 30 | api.method = method; 31 | api.isNeedSign = isNeedSign; 32 | API_FUNC_MAP.put(cmd,api); 33 | } 34 | //页面api接口 end 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/controller/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.controller; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.example.springboot.config.Config; 5 | import com.example.springboot.dto.ApiFuncData; 6 | import com.example.springboot.dto.Result; 7 | import com.example.springboot.util.JSONUtil; 8 | import com.example.springboot.util.SpringContextHolder; 9 | import org.apache.commons.io.IOUtils; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.commons.logging.Log; 12 | import org.apache.commons.logging.LogFactory; 13 | import org.apache.shiro.SecurityUtils; 14 | import org.apache.shiro.authc.LockedAccountException; 15 | import org.apache.shiro.authz.UnauthenticatedException; 16 | import org.apache.shiro.authz.UnauthorizedException; 17 | import org.apache.shiro.subject.Subject; 18 | import org.apache.shiro.util.ThreadContext; 19 | import org.springframework.stereotype.Controller; 20 | import org.springframework.web.bind.annotation.CrossOrigin; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.ResponseBody; 23 | 24 | import javax.servlet.http.HttpServletRequest; 25 | import java.lang.reflect.InvocationTargetException; 26 | import java.lang.reflect.Method; 27 | 28 | @CrossOrigin 29 | @Controller 30 | public class ApiController { 31 | Log log = LogFactory.getLog(ApiController.class); 32 | @RequestMapping(value = "/api",produces = "application/json;charset=UTF-8") 33 | @ResponseBody 34 | public String api(HttpServletRequest request) { 35 | Result result = new Result(1); 36 | // ThreadContext.unbindSubject(); 37 | // if(StringUtils.isNotEmpty(request.getHeader("X-TOKEN"))) { 38 | // Subject subject = (new Subject.Builder()).sessionId(request.getHeader("X-TOKEN")).buildSubject(); 39 | // if (subject != null) { 40 | // ThreadContext.bind(subject); 41 | // } 42 | // } 43 | try { 44 | String jsonString = IOUtils.toString(request.getInputStream(), "utf-8"); 45 | log.info("收到请求:" + jsonString); 46 | JSONObject json = JSONObject.parseObject(jsonString); 47 | json.put("clientIp", request.getRemoteAddr()); 48 | json.put("referer", request.getHeader("referer")); 49 | json.put("hostUrl", request.getScheme() +"://" + request.getServerName()); 50 | 51 | String cmd = json.getString("cmd"); 52 | do { 53 | if (StringUtils.isEmpty(cmd)) { 54 | result.message = "缺失参数"; 55 | break; 56 | } else if (!Config.API_FUNC_MAP.containsKey(cmd)) { 57 | result.message = "接口不存在"; 58 | break; 59 | } 60 | 61 | ApiFuncData api = Config.API_FUNC_MAP.get(cmd); 62 | Object o = SpringContextHolder.getBean(api.beanName); 63 | Method callMethod = o.getClass().getMethod(api.method, JSONObject.class); 64 | 65 | result = (Result) callMethod.invoke(o, json); 66 | 67 | } while (false); 68 | }catch (InvocationTargetException e) { 69 | if(e.getTargetException() instanceof LockedAccountException){ 70 | result.message = "用户被冻结~"; 71 | }else if(e.getTargetException() instanceof UnauthenticatedException){ 72 | result.message = "需要登录~"; 73 | }else if(e.getTargetException() instanceof UnauthorizedException){ 74 | result.message = "权限不足~"; 75 | }else if(e.getTargetException() instanceof RuntimeException){ 76 | if(e.getTargetException().getMessage() == null){ 77 | log.error("调用接口错误:",e.getTargetException()); 78 | }else{ 79 | log.error(e.getTargetException().getMessage()); 80 | } 81 | }else { 82 | log.error("调用接口错误:", e.getTargetException()); 83 | } 84 | }catch (Exception e) { 85 | log.error("调用接口错误:", e); 86 | result.message = e.getMessage(); 87 | } 88 | 89 | String retJson = JSONUtil.toJson(result); 90 | log.info("返回:" + retJson); 91 | return retJson; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.controller; 2 | 3 | import com.example.springboot.dto.Result; 4 | import com.example.springboot.gentry.DnChannel; 5 | import com.example.springboot.gentry.DnChannelMapper; 6 | import com.example.springboot.mybatis.PageHelper; 7 | import org.apache.commons.logging.Log; 8 | import org.apache.commons.logging.LogFactory; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | @Controller 19 | @RequestMapping("/") 20 | public class IndexController { 21 | Log log = LogFactory.getLog(IndexController.class); 22 | 23 | @Resource 24 | DnChannelMapper mapper; 25 | 26 | @RequestMapping 27 | public String index(){ 28 | return "/dist/index.html"; 29 | } 30 | 31 | @RequestMapping("/test") 32 | public void test(){ 33 | try { 34 | PageHelper.page(1,10); 35 | log.error(mapper.getUserPromotionByPage(100438,"2018-10-10%",null)); 36 | // mapper.selectAll(); 37 | PageHelper.page(1,5); 38 | log.error(mapper.selectAll()); 39 | //TODO 因为使用了缓存,导致第二次查询没有启用prepare的Interceptor,返回了第一次查询的数据 40 | PageHelper.page(2,5); 41 | log.error(mapper.selectAll()); 42 | }catch (Exception e){ 43 | log.error(e); 44 | } 45 | } 46 | 47 | @RequestMapping("/unauth") 48 | @ResponseBody 49 | public Result unauth(){ 50 | Result result = new Result(100); 51 | result.message = "未登录"; 52 | return result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/dao/BaseDao.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.dao; 2 | 3 | import org.hibernate.Session; 4 | import org.springframework.data.repository.NoRepositoryBean; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | /** 11 | * Created by apple on 2018/4/23. 12 | */ 13 | @NoRepositoryBean 14 | public interface BaseDao { 15 | 16 | public Optional findByIdWithLock(ID id); 17 | 18 | public List findBySql(String sql, Object... values); 19 | 20 | public List findMapBySql(String sql, Object... values); 21 | 22 | public Object findUniqueMapBySql(String sql, Object... values); 23 | 24 | public List findListBySql(String sql,Object... values); 25 | 26 | public List findByProperty(String prop, Object value); 27 | 28 | public T findUniqueByProperty(String prop, Object value); 29 | 30 | public void executeUpdateSql(String sql); 31 | 32 | public int executeUpdateSqlWithReturn(String sql); 33 | public int executeUpdateSqlWithReturn(String sql, Object... values); 34 | } -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/dao/DnPromotionInfoDao.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.dao; 2 | 3 | 4 | import com.example.springboot.entity.DnPromotionInfoEntity; 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | /** 8 | * Created by apple on 2018/4/24. 9 | */ 10 | public interface DnPromotionInfoDao extends CrudRepository,BaseDao{ 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/dao/SysMenuDao.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.dao; 2 | 3 | import com.example.springboot.entity.SysMenuEntity; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface SysMenuDao extends CrudRepository,BaseDao { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/dao/SysUserDao.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.dao; 2 | 3 | import com.example.springboot.entity.SysUserEntity; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface SysUserDao extends CrudRepository,BaseDao { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/dao/impl/BaseDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.dao.impl; 2 | 3 | import com.example.springboot.dao.BaseDao; 4 | import jdk.nashorn.internal.runtime.options.Option; 5 | import org.hibernate.LockMode; 6 | import org.hibernate.Session; 7 | import org.hibernate.query.NativeQuery; 8 | import org.hibernate.query.Query; 9 | import org.hibernate.transform.Transformers; 10 | import org.springframework.data.jpa.repository.Lock; 11 | import org.springframework.data.jpa.repository.support.JpaEntityInformation; 12 | import org.springframework.data.jpa.repository.support.SimpleJpaRepository; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import javax.persistence.EntityManager; 16 | import javax.persistence.LockModeType; 17 | import javax.persistence.criteria.CriteriaBuilder; 18 | import javax.persistence.criteria.CriteriaQuery; 19 | import javax.persistence.criteria.Root; 20 | import java.io.Serializable; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | /** 27 | * Created by apple on 2018/4/23. 28 | */ 29 | @Transactional 30 | public class BaseDaoImpl extends SimpleJpaRepository implements BaseDao { 31 | 32 | private EntityManager entityManager; 33 | 34 | 35 | public BaseDaoImpl(JpaEntityInformation entityInformation, 36 | EntityManager entityManager) { 37 | super(entityInformation, entityManager); 38 | this.entityManager = entityManager; 39 | } 40 | 41 | public Session getSession(){ 42 | return entityManager.unwrap(Session.class); 43 | } 44 | 45 | public Optional findByIdWithLock(ID id){ 46 | return Optional.ofNullable(getSession().get(getDomainClass(),id, LockMode.PESSIMISTIC_WRITE)); 47 | } 48 | 49 | private NativeQuery createSqlQuery(String sql , Object... values){ 50 | NativeQuery query = getSession().createNativeQuery(sql); 51 | for(int i = 0;i < values.length;++i){ 52 | query.setParameter(i + 1,values[i]); 53 | } 54 | return query; 55 | 56 | } 57 | 58 | @SuppressWarnings("unchecked") 59 | public List findBySql(String sql, Object... values){ 60 | return createSqlQuery(sql,values).addEntity(getDomainClass()).list(); 61 | } 62 | 63 | public List findMapBySql(String sql,Object... values){ 64 | return createSqlQuery(sql,values).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list(); 65 | } 66 | 67 | public List findListBySql(String sql,Object... values){ 68 | return createSqlQuery(sql,values).getResultList(); 69 | } 70 | 71 | public Object findUniqueMapBySql(String sql,Object... values){ 72 | return createSqlQuery(sql,values).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).uniqueResult(); 73 | } 74 | 75 | private Query createCriteria(Map params){ 76 | CriteriaBuilder cb = getSession().getCriteriaBuilder(); 77 | CriteriaQuery cq = cb.createQuery(getDomainClass()); 78 | Root root = cq.from(getDomainClass()); 79 | 80 | for(Map.Entry entry : params.entrySet()) { 81 | cq.where(cb.equal(root.get(entry.getKey()), entry.getValue())); 82 | } 83 | 84 | return getSession().createQuery(cq); 85 | } 86 | 87 | public List findByProperty(String prop,Object value){ 88 | return createCriteria(Collections.singletonMap(prop,value)).getResultList(); 89 | } 90 | 91 | public T findUniqueByProperty(String prop,Object value){ 92 | try{ 93 | return createCriteria(Collections.singletonMap(prop,value)).getSingleResult(); 94 | }catch (Exception e){ 95 | } 96 | return null; 97 | } 98 | 99 | public void executeUpdateSql(String sql){ 100 | this.getSession().createNativeQuery(sql).executeUpdate(); 101 | } 102 | 103 | public int executeUpdateSqlWithReturn(String sql){ 104 | return this.getSession().createNativeQuery(sql).executeUpdate(); 105 | } 106 | 107 | public int executeUpdateSqlWithReturn(String sql,Object... values){ 108 | return createSqlQuery(sql,values).executeUpdate(); 109 | } 110 | } -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/dto/ApiFuncData.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.dto; 2 | 3 | /** 4 | * Created by apple on 2018/4/23. 5 | */ 6 | public class ApiFuncData { 7 | public String beanName; 8 | public String method; 9 | public boolean isNeedSign; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/dto/Result.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.dto; 2 | 3 | public class Result { 4 | public int code; 5 | public String message; 6 | public Object body; 7 | 8 | public Result(){ 9 | code = 20000; 10 | } 11 | public Result(int code){ 12 | this.code = code; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/entity/DnPromotionInfoEntity.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.entity; 2 | 3 | import org.hibernate.annotations.DynamicInsert; 4 | 5 | import javax.persistence.*; 6 | import java.sql.Timestamp; 7 | 8 | @Entity 9 | @Table(name = "dn_promotion_info") 10 | @DynamicInsert 11 | public class DnPromotionInfoEntity { 12 | private Integer id; 13 | private String channelCode; 14 | private String yybImei1; 15 | private String yybImei2; 16 | private String imei1; 17 | private String imei2; 18 | private String imsi; 19 | private String model; 20 | private String company; 21 | private String brand; 22 | private String mac; 23 | private String wifiId; 24 | private String hardware; 25 | private String androidId; 26 | private String androidApi; 27 | private String oldImei; 28 | private Integer income; 29 | private Byte sceneId; 30 | private Byte flag; 31 | private Byte state; 32 | private Timestamp updTime; 33 | private Timestamp createTime; 34 | private Integer channelId; 35 | 36 | @Id 37 | @GeneratedValue(strategy = GenerationType.IDENTITY) 38 | @Column(name = "id") 39 | public Integer getId() { 40 | return id; 41 | } 42 | 43 | public void setId(Integer id) { 44 | this.id = id; 45 | } 46 | 47 | @Basic 48 | @Column(name = "channel_code") 49 | public String getChannelCode() { 50 | return channelCode; 51 | } 52 | 53 | public void setChannelCode(String channelCode) { 54 | this.channelCode = channelCode; 55 | } 56 | 57 | @Basic 58 | @Column(name = "yyb_imei1") 59 | public String getYybImei1() { 60 | return yybImei1; 61 | } 62 | 63 | public void setYybImei1(String yybImei1) { 64 | this.yybImei1 = yybImei1; 65 | } 66 | 67 | @Basic 68 | @Column(name = "yyb_imei2") 69 | public String getYybImei2() { 70 | return yybImei2; 71 | } 72 | 73 | public void setYybImei2(String yybImei2) { 74 | this.yybImei2 = yybImei2; 75 | } 76 | 77 | @Basic 78 | @Column(name = "imei1") 79 | public String getImei1() { 80 | return imei1; 81 | } 82 | 83 | public void setImei1(String imei1) { 84 | this.imei1 = imei1; 85 | } 86 | 87 | @Basic 88 | @Column(name = "imei2") 89 | public String getImei2() { 90 | return imei2; 91 | } 92 | 93 | public void setImei2(String imei2) { 94 | this.imei2 = imei2; 95 | } 96 | 97 | @Basic 98 | @Column(name = "imsi") 99 | public String getImsi() { 100 | return imsi; 101 | } 102 | 103 | public void setImsi(String imsi) { 104 | this.imsi = imsi; 105 | } 106 | 107 | @Basic 108 | @Column(name = "entity") 109 | public String getModel() { 110 | return model; 111 | } 112 | 113 | public void setModel(String model) { 114 | this.model = model; 115 | } 116 | 117 | @Basic 118 | @Column(name = "company") 119 | public String getCompany() { 120 | return company; 121 | } 122 | 123 | public void setCompany(String company) { 124 | this.company = company; 125 | } 126 | 127 | @Basic 128 | @Column(name = "brand") 129 | public String getBrand() { 130 | return brand; 131 | } 132 | 133 | public void setBrand(String brand) { 134 | this.brand = brand; 135 | } 136 | 137 | @Basic 138 | @Column(name = "mac") 139 | public String getMac() { 140 | return mac; 141 | } 142 | 143 | public void setMac(String mac) { 144 | this.mac = mac; 145 | } 146 | 147 | @Basic 148 | @Column(name = "wifi_id") 149 | public String getWifiId() { 150 | return wifiId; 151 | } 152 | 153 | public void setWifiId(String wifiId) { 154 | this.wifiId = wifiId; 155 | } 156 | 157 | @Basic 158 | @Column(name = "hardware") 159 | public String getHardware() { 160 | return hardware; 161 | } 162 | 163 | public void setHardware(String hardware) { 164 | this.hardware = hardware; 165 | } 166 | 167 | @Basic 168 | @Column(name = "android_id") 169 | public String getAndroidId() { 170 | return androidId; 171 | } 172 | 173 | public void setAndroidId(String androidId) { 174 | this.androidId = androidId; 175 | } 176 | 177 | @Basic 178 | @Column(name = "android_api") 179 | public String getAndroidApi() { 180 | return androidApi; 181 | } 182 | 183 | public void setAndroidApi(String androidApi) { 184 | this.androidApi = androidApi; 185 | } 186 | 187 | @Basic 188 | @Column(name = "oldImei") 189 | public String getOldImei() { 190 | return oldImei; 191 | } 192 | 193 | public void setOldImei(String oldImei) { 194 | this.oldImei = oldImei; 195 | } 196 | 197 | @Basic 198 | @Column(name = "income") 199 | public Integer getIncome() { 200 | return income; 201 | } 202 | 203 | public void setIncome(Integer income) { 204 | this.income = income; 205 | } 206 | 207 | @Basic 208 | @Column(name = "sceneId") 209 | public Byte getSceneId() { 210 | return sceneId; 211 | } 212 | 213 | public void setSceneId(Byte sceneId) { 214 | this.sceneId = sceneId; 215 | } 216 | 217 | @Basic 218 | @Column(name = "flag") 219 | public Byte getFlag() { 220 | return flag; 221 | } 222 | 223 | public void setFlag(Byte flag) { 224 | this.flag = flag; 225 | } 226 | 227 | @Basic 228 | @Column(name = "state") 229 | public Byte getState() { 230 | return state; 231 | } 232 | 233 | public void setState(Byte state) { 234 | this.state = state; 235 | } 236 | 237 | @Basic 238 | @Column(name = "upd_time",insertable = false,updatable = false) 239 | public Timestamp getUpdTime() { 240 | return updTime; 241 | } 242 | 243 | public void setUpdTime(Timestamp updTime) { 244 | this.updTime = updTime; 245 | } 246 | 247 | @Basic 248 | @Column(name = "create_time",insertable = false,updatable = false) 249 | public Timestamp getCreateTime() { 250 | return createTime; 251 | } 252 | 253 | public void setCreateTime(Timestamp createTime) { 254 | this.createTime = createTime; 255 | } 256 | 257 | @Basic 258 | @Column(name = "channel_Id") 259 | public Integer getChannelId() { 260 | return channelId; 261 | } 262 | 263 | public void setChannelId(Integer channelId) { 264 | this.channelId = channelId; 265 | } 266 | 267 | @Override 268 | public boolean equals(Object o) { 269 | if (this == o) return true; 270 | if (o == null || getClass() != o.getClass()) return false; 271 | 272 | DnPromotionInfoEntity entity = (DnPromotionInfoEntity) o; 273 | 274 | if (channelId != entity.channelId) return false; 275 | 276 | return true; 277 | } 278 | 279 | @Override 280 | public int hashCode() { 281 | return channelId; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/entity/SysMenuEntity.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.entity; 2 | 3 | import javax.persistence.*; 4 | import java.util.Date; 5 | import java.util.Objects; 6 | 7 | @Entity 8 | @Table(name = "sys_menu", schema = "test", catalog = "") 9 | public class SysMenuEntity { 10 | private Integer id; 11 | private String menuName; 12 | private String menuPath; 13 | private Integer permissionId; 14 | private Date createTime; 15 | private Date updateTime; 16 | 17 | @Id 18 | @Column(name = "id") 19 | public Integer getId() { 20 | return id; 21 | } 22 | 23 | public void setId(Integer id) { 24 | this.id = id; 25 | } 26 | 27 | @Basic 28 | @Column(name = "menu_name") 29 | public String getMenuName() { 30 | return menuName; 31 | } 32 | 33 | public void setMenuName(String menuName) { 34 | this.menuName = menuName; 35 | } 36 | 37 | @Basic 38 | @Column(name = "menu_path") 39 | public String getMenuPath() { 40 | return menuPath; 41 | } 42 | 43 | public void setMenuPath(String menuPath) { 44 | this.menuPath = menuPath; 45 | } 46 | 47 | @Basic 48 | @Column(name = "permission_id") 49 | public Integer getPermissionId() { 50 | return permissionId; 51 | } 52 | 53 | public void setPermissionId(Integer permissionId) { 54 | this.permissionId = permissionId; 55 | } 56 | 57 | @Basic 58 | @Column(name = "create_time") 59 | public Date getCreateTime() { 60 | return createTime; 61 | } 62 | 63 | public void setCreateTime(Date createTime) { 64 | this.createTime = createTime; 65 | } 66 | 67 | @Basic 68 | @Column(name = "update_time") 69 | public Date getUpdateTime() { 70 | return updateTime; 71 | } 72 | 73 | public void setUpdateTime(Date updateTime) { 74 | this.updateTime = updateTime; 75 | } 76 | 77 | @Override 78 | public boolean equals(Object o) { 79 | if (this == o) return true; 80 | if (o == null || getClass() != o.getClass()) return false; 81 | SysMenuEntity that = (SysMenuEntity) o; 82 | return Objects.equals(id, that.id) && 83 | Objects.equals(menuName, that.menuName) && 84 | Objects.equals(menuPath, that.menuPath) && 85 | Objects.equals(permissionId, that.permissionId) && 86 | Objects.equals(createTime, that.createTime) && 87 | Objects.equals(updateTime, that.updateTime); 88 | } 89 | 90 | @Override 91 | public int hashCode() { 92 | return Objects.hash(id, menuName, menuPath, permissionId, createTime, updateTime); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/entity/SysPermissionEntity.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.entity; 2 | 3 | import javax.persistence.*; 4 | import java.util.Date; 5 | import java.util.Objects; 6 | 7 | @Entity 8 | @Table(name = "sys_permission", schema = "test", catalog = "") 9 | public class SysPermissionEntity { 10 | private Integer id; 11 | private String permissionCode; 12 | private String permissionDesc; 13 | private Date createTime; 14 | 15 | @Id 16 | @Column(name = "id") 17 | public Integer getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Integer id) { 22 | this.id = id; 23 | } 24 | 25 | @Basic 26 | @Column(name = "permission_code") 27 | public String getPermissionCode() { 28 | return permissionCode; 29 | } 30 | 31 | public void setPermissionCode(String permissionCode) { 32 | this.permissionCode = permissionCode; 33 | } 34 | 35 | @Basic 36 | @Column(name = "permission_desc") 37 | public String getPermissionDesc() { 38 | return permissionDesc; 39 | } 40 | 41 | public void setPermissionDesc(String permissionDesc) { 42 | this.permissionDesc = permissionDesc; 43 | } 44 | 45 | @Basic 46 | @Column(name = "create_time") 47 | public Date getCreateTime() { 48 | return createTime; 49 | } 50 | 51 | public void setCreateTime(Date createTime) { 52 | this.createTime = createTime; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (this == o) return true; 58 | if (o == null || getClass() != o.getClass()) return false; 59 | SysPermissionEntity that = (SysPermissionEntity) o; 60 | return Objects.equals(id, that.id) && 61 | Objects.equals(permissionCode, that.permissionCode) && 62 | Objects.equals(permissionDesc, that.permissionDesc) && 63 | Objects.equals(createTime, that.createTime); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Objects.hash(id, permissionCode, permissionDesc, createTime); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/entity/SysRoleEntity.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.entity; 2 | 3 | import javax.persistence.*; 4 | import java.util.Date; 5 | import java.util.Objects; 6 | 7 | @Entity 8 | @Table(name = "sys_role", schema = "test", catalog = "") 9 | public class SysRoleEntity { 10 | private Integer id; 11 | private String rolename; 12 | private Date createTime; 13 | 14 | @Id 15 | @Column(name = "id") 16 | public Integer getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Integer id) { 21 | this.id = id; 22 | } 23 | 24 | @Basic 25 | @Column(name = "rolename") 26 | public String getRolename() { 27 | return rolename; 28 | } 29 | 30 | public void setRolename(String rolename) { 31 | this.rolename = rolename; 32 | } 33 | 34 | @Basic 35 | @Column(name = "create_time") 36 | public Date getCreateTime() { 37 | return createTime; 38 | } 39 | 40 | public void setCreateTime(Date createTime) { 41 | this.createTime = createTime; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | SysRoleEntity that = (SysRoleEntity) o; 49 | return Objects.equals(id, that.id) && 50 | Objects.equals(rolename, that.rolename) && 51 | Objects.equals(createTime, that.createTime); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hash(id, rolename, createTime); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/entity/SysRolePermissionEntity.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.entity; 2 | 3 | import javax.persistence.*; 4 | import java.util.Date; 5 | import java.util.Objects; 6 | 7 | @Entity 8 | @Table(name = "sys_role_permission", schema = "test", catalog = "") 9 | public class SysRolePermissionEntity { 10 | private Integer id; 11 | private Integer roleId; 12 | private Integer permissionId; 13 | private Date createTime; 14 | 15 | @Id 16 | @Column(name = "id") 17 | public Integer getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Integer id) { 22 | this.id = id; 23 | } 24 | 25 | @Basic 26 | @Column(name = "role_id") 27 | public Integer getRoleId() { 28 | return roleId; 29 | } 30 | 31 | public void setRoleId(Integer roleId) { 32 | this.roleId = roleId; 33 | } 34 | 35 | @Basic 36 | @Column(name = "permission_id") 37 | public Integer getPermissionId() { 38 | return permissionId; 39 | } 40 | 41 | public void setPermissionId(Integer permissionId) { 42 | this.permissionId = permissionId; 43 | } 44 | 45 | @Basic 46 | @Column(name = "create_time") 47 | public Date getCreateTime() { 48 | return createTime; 49 | } 50 | 51 | public void setCreateTime(Date createTime) { 52 | this.createTime = createTime; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (this == o) return true; 58 | if (o == null || getClass() != o.getClass()) return false; 59 | SysRolePermissionEntity that = (SysRolePermissionEntity) o; 60 | return Objects.equals(id, that.id) && 61 | Objects.equals(roleId, that.roleId) && 62 | Objects.equals(permissionId, that.permissionId) && 63 | Objects.equals(createTime, that.createTime); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Objects.hash(id, roleId, permissionId, createTime); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/entity/SysUserEntity.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.entity; 2 | 3 | import javax.persistence.*; 4 | import java.io.Serializable; 5 | import java.util.Date; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | 10 | @Entity 11 | @Table(name = "sys_user", schema = "test", catalog = "") 12 | public class SysUserEntity implements Serializable { 13 | public interface STATE { 14 | int NORMAL = 1; 15 | int INVALID = 2; 16 | } 17 | private Integer id; 18 | private String username; 19 | private String password; 20 | private String salt; 21 | private String nickname; 22 | private Date createTime; 23 | private Date updateTime; 24 | private Integer state; 25 | private String avatar; 26 | 27 | private List roleList; 28 | private List permissionList; 29 | private List> menuList; 30 | 31 | @Id 32 | @Column(name = "id") 33 | public Integer getId() { 34 | return id; 35 | } 36 | 37 | public void setId(Integer id) { 38 | this.id = id; 39 | } 40 | 41 | @Basic 42 | @Column(name = "username") 43 | public String getUsername() { 44 | return username; 45 | } 46 | 47 | public void setUsername(String username) { 48 | this.username = username; 49 | } 50 | 51 | @Basic 52 | @Column(name = "password") 53 | public String getPassword() { 54 | return password; 55 | } 56 | 57 | public void setPassword(String password) { 58 | this.password = password; 59 | } 60 | 61 | @Basic 62 | @Column(name = "salt") 63 | public String getSalt() { 64 | return salt; 65 | } 66 | 67 | public void setSalt(String salt) { 68 | this.salt = salt; 69 | } 70 | 71 | @Basic 72 | @Column(name = "nickname") 73 | public String getNickname() { 74 | return nickname; 75 | } 76 | 77 | public void setNickname(String nickname) { 78 | this.nickname = nickname; 79 | } 80 | 81 | @Basic 82 | @Column(name = "create_time") 83 | public Date getCreateTime() { 84 | return createTime; 85 | } 86 | 87 | public void setCreateTime(Date createTime) { 88 | this.createTime = createTime; 89 | } 90 | 91 | @Basic 92 | @Column(name = "update_time") 93 | public Date getUpdateTime() { 94 | return updateTime; 95 | } 96 | 97 | public void setUpdateTime(Date updateTime) { 98 | this.updateTime = updateTime; 99 | } 100 | 101 | @Basic 102 | @Column(name = "state") 103 | public Integer getState() { 104 | return state; 105 | } 106 | 107 | public void setState(Integer state) { 108 | this.state = state; 109 | } 110 | 111 | @Basic 112 | @Column(name = "avatar") 113 | public String getAvatar() { 114 | return avatar; 115 | } 116 | 117 | public void setAvatar(String avatar) { 118 | this.avatar = avatar; 119 | } 120 | 121 | @Transient 122 | public List getRoleList() { 123 | return roleList; 124 | } 125 | 126 | 127 | public void setRoleList(List roleList) { 128 | this.roleList = roleList; 129 | } 130 | @Transient 131 | public List getPermissionList() { 132 | return permissionList; 133 | } 134 | 135 | public void setPermissionList(List permissionList) { 136 | this.permissionList = permissionList; 137 | } 138 | 139 | @Transient 140 | public List> getMenuList() { 141 | return menuList; 142 | } 143 | 144 | public void setMenuList(List> menuList) { 145 | this.menuList = menuList; 146 | } 147 | 148 | @Override 149 | public boolean equals(Object o) { 150 | if (this == o) return true; 151 | if (o == null || getClass() != o.getClass()) return false; 152 | SysUserEntity that = (SysUserEntity) o; 153 | return Objects.equals(id, that.id) && 154 | Objects.equals(username, that.username) && 155 | Objects.equals(password, that.password) && 156 | Objects.equals(salt, that.salt) && 157 | Objects.equals(nickname, that.nickname) && 158 | Objects.equals(createTime, that.createTime) && 159 | Objects.equals(updateTime, that.updateTime) && 160 | Objects.equals(state, that.state); 161 | } 162 | 163 | @Override 164 | public int hashCode() { 165 | return Objects.hash(id, username, password, salt, nickname, createTime, updateTime, state); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/entity/SysUserRoleEntity.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.entity; 2 | 3 | import javax.persistence.*; 4 | import java.util.Date; 5 | import java.util.Objects; 6 | 7 | @Entity 8 | @Table(name = "sys_user_role", schema = "test", catalog = "") 9 | public class SysUserRoleEntity { 10 | private Integer id; 11 | private Integer userId; 12 | private Integer roleId; 13 | private Date createTime; 14 | 15 | @Id 16 | @Column(name = "id") 17 | public Integer getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Integer id) { 22 | this.id = id; 23 | } 24 | 25 | @Basic 26 | @Column(name = "user_id") 27 | public Integer getUserId() { 28 | return userId; 29 | } 30 | 31 | public void setUserId(Integer userId) { 32 | this.userId = userId; 33 | } 34 | 35 | @Basic 36 | @Column(name = "role_id") 37 | public Integer getRoleId() { 38 | return roleId; 39 | } 40 | 41 | public void setRoleId(Integer roleId) { 42 | this.roleId = roleId; 43 | } 44 | 45 | @Basic 46 | @Column(name = "create_time") 47 | public Date getCreateTime() { 48 | return createTime; 49 | } 50 | 51 | public void setCreateTime(Date createTime) { 52 | this.createTime = createTime; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (this == o) return true; 58 | if (o == null || getClass() != o.getClass()) return false; 59 | SysUserRoleEntity that = (SysUserRoleEntity) o; 60 | return Objects.equals(id, that.id) && 61 | Objects.equals(userId, that.userId) && 62 | Objects.equals(roleId, that.roleId) && 63 | Objects.equals(createTime, that.createTime); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Objects.hash(id, userId, roleId, createTime); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/gentry/DnChannelMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.gentry; 2 | 3 | import com.example.springboot.gentry.DnChannel; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public interface DnChannelMapper { 10 | int deleteByPrimaryKey(Integer id); 11 | 12 | int insert(DnChannel record); 13 | 14 | DnChannel selectByPrimaryKey(Integer id); 15 | 16 | List selectAll(); 17 | 18 | int updateByPrimaryKey(DnChannel record); 19 | 20 | List getSummary(Integer channelId); 21 | 22 | List getUserPromotionByPage(@Param("channelId") Integer channelId, 23 | @Param("startTime") String startTime, 24 | @Param("endTime") String endTime); 25 | } -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/gentry/DnPromotionInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.gentry; 2 | 3 | import com.example.springboot.gentry.DnPromotionInfo; 4 | import java.util.List; 5 | 6 | public interface DnPromotionInfoMapper { 7 | int deleteByPrimaryKey(Integer id); 8 | 9 | int insert(DnPromotionInfo record); 10 | 11 | DnPromotionInfo selectByPrimaryKey(Integer id); 12 | 13 | List selectAll(); 14 | 15 | int updateByPrimaryKey(DnPromotionInfo record); 16 | } -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/mybatis/PageHelper.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.mybatis; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PageHelper { 7 | private static ThreadLocal> threadMap = new ThreadLocal>(); 8 | private static final String TOTAL_COUNT = "TOTAL_COUNT"; 9 | private static final String PAGE_NO = "PAGE_NO"; 10 | private static final String PAGE_SIZE = "PAGE_SIZE"; 11 | 12 | private static void setValue(String key,Object value){ 13 | if(threadMap.get() == null){ 14 | threadMap.set(new HashMap<>()); 15 | } 16 | threadMap.get().put(key,value); 17 | } 18 | private static Object getValue(String key){ 19 | return threadMap.get() == null?null:threadMap.get().get(key); 20 | } 21 | 22 | private static void remove(String key){ 23 | if(threadMap.get() != null){ 24 | threadMap.get().remove(key); 25 | } 26 | } 27 | 28 | public static void page(int pageNo,int pageSize){ 29 | setValue(PAGE_NO,pageNo); 30 | setValue(PAGE_SIZE,pageSize); 31 | } 32 | public static Long getTotal(){ 33 | return (Long)getValue(TOTAL_COUNT); 34 | } 35 | 36 | public static void setTotal(Long value){ 37 | setValue(TOTAL_COUNT,value); 38 | } 39 | 40 | public static Integer getPage(){ 41 | return (Integer) getValue(PAGE_NO); 42 | } 43 | 44 | public static Integer getPageSize(){ 45 | return (Integer) getValue(PAGE_SIZE); 46 | } 47 | 48 | public static void clear(){ 49 | remove(PAGE_NO); 50 | remove(PAGE_SIZE); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/mybatis/PageInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.mybatis; 2 | 3 | import org.apache.ibatis.binding.MapperMethod; 4 | import org.apache.ibatis.executor.Executor; 5 | import org.apache.ibatis.executor.statement.StatementHandler; 6 | import org.apache.ibatis.mapping.BoundSql; 7 | import org.apache.ibatis.mapping.MappedStatement; 8 | import org.apache.ibatis.mapping.ParameterMapping; 9 | import org.apache.ibatis.plugin.*; 10 | import org.apache.ibatis.reflection.SystemMetaObject; 11 | import org.apache.ibatis.session.ResultHandler; 12 | import org.apache.ibatis.session.RowBounds; 13 | import org.apache.ibatis.type.UnknownTypeHandler; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.sql.Connection; 17 | import java.sql.PreparedStatement; 18 | import java.sql.ResultSet; 19 | import java.util.*; 20 | 21 | @Intercepts({@Signature( 22 | type = StatementHandler.class, 23 | method = "prepare", 24 | args = {Connection.class,Integer.class}), 25 | @Signature(type = Executor.class, 26 | method = "query", 27 | args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})}) 28 | @Component 29 | public class PageInterceptor implements Interceptor { 30 | 31 | /* 32 | 分页问题: 33 | 1.效率:粗暴的用子查询,效率慢,但简单 34 | 解决: 35 | * 1)分情况处理,如果有分组函数,group by,distinct等,则用子查询,否则直接使用select count(1) from替换原from前sql; 36 | * 2)自定义查询sql,Interceptor中检测如果有自定义则优先使用——PageHelper后面更新的版本使用的这样的方式; 37 | 2.缓存:当在同一个事务中时,mybatis相同的接口会直接使用缓存中数据 38 | 解决: 39 | * 1)修改Interceptor位置,在prepare中由于缓存逻辑先进行,是没办法拦截的,如果拦截query则可以判断分页参数来决定缓存逻辑 40 | * 2)不处理,同一个请求中,需要连续分页的逻辑基本没有,但要记住这里有个坑 41 | * 3)直接不启用mybatis缓存,但有点本末倒置了 42 | */ 43 | @Override 44 | public Object intercept(Invocation invocation) throws Throwable { 45 | try { 46 | Integer pageNo = PageHelper.getPage(); 47 | Integer pageSize = PageHelper.getPageSize(); 48 | if(pageNo == null || pageSize == null){ 49 | return invocation.proceed(); 50 | } 51 | 52 | if(invocation.getTarget() instanceof Executor){ 53 | //修改MappedStatement.id,确保缓存与分页关联 54 | MappedStatement ms = (MappedStatement)invocation.getArgs()[0]; 55 | SystemMetaObject.forObject(ms).setValue("id",ms.getId()+pageNo+pageSize); 56 | return invocation.proceed(); 57 | } 58 | StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); 59 | BoundSql sql = statementHandler.getBoundSql(); 60 | System.out.println("sql:" + sql.getSql()); 61 | MappedStatement mappedStatement = (MappedStatement) SystemMetaObject.forObject(statementHandler) 62 | .getValue("delegate.mappedStatement"); 63 | 64 | String countSql = "select count(1) from (" + sql.getSql() + ") t"; 65 | System.out.println("count sql:" + sql.getSql()); 66 | Connection conn = (Connection) invocation.getArgs()[0]; 67 | PreparedStatement pstmt = conn.prepareStatement(countSql); 68 | for (int i = 0; i < sql.getParameterMappings().size(); i++) { 69 | ParameterMapping pm = sql.getParameterMappings().get(i); 70 | pstmt.setObject(i + 1, ((Map) (sql.getParameterObject())).get(pm.getProperty())); 71 | } 72 | ResultSet rs = pstmt.executeQuery(); 73 | if (rs.next()) { 74 | long totalCount = rs.getBigDecimal(1).longValue(); 75 | PageHelper.setTotal(totalCount); 76 | } else { 77 | PageHelper.setTotal(0L); 78 | } 79 | String newSql = sql.getSql() + " limit "+(pageNo-1)*pageSize+","+pageSize; 80 | 81 | SystemMetaObject.forObject(sql).setValue("sql",newSql); 82 | 83 | return invocation.proceed(); 84 | }finally { 85 | PageHelper.clear(); 86 | } 87 | } 88 | 89 | @Override 90 | public Object plugin(Object target) { 91 | return Plugin.wrap(target,this); 92 | } 93 | 94 | @Override 95 | public void setProperties(Properties properties) { 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/service/ApiService.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.example.springboot.dao.SysMenuDao; 6 | import com.example.springboot.dao.SysUserDao; 7 | import com.example.springboot.dto.Result; 8 | import com.example.springboot.entity.SysUserEntity; 9 | import com.example.springboot.util.ValidateUtil; 10 | import org.apache.commons.logging.Log; 11 | import org.apache.commons.logging.LogFactory; 12 | import org.apache.shiro.SecurityUtils; 13 | import org.apache.shiro.authc.UsernamePasswordToken; 14 | import org.apache.shiro.authz.annotation.RequiresAuthentication; 15 | import org.apache.shiro.mgt.SecurityManager; 16 | import org.apache.shiro.subject.Subject; 17 | import org.springframework.stereotype.Component; 18 | 19 | import javax.annotation.PostConstruct; 20 | import javax.annotation.Resource; 21 | 22 | @Component 23 | public class ApiService { 24 | Log log = LogFactory.getLog(ApiService.class); 25 | @Resource 26 | SysUserDao sysUserDao; 27 | @Resource 28 | SysMenuDao sysMenuDao; 29 | @Resource 30 | private SecurityManager securityManager; 31 | 32 | @PostConstruct 33 | private void initStaticSecurityManager() { 34 | SecurityUtils.setSecurityManager(securityManager); 35 | } 36 | 37 | public Result signin(JSONObject json){ 38 | ValidateUtil.validJSONParam(json, "username","password"); 39 | 40 | String username = json.getString("username"); 41 | String password = json.getString("password"); 42 | 43 | Result result = new Result(); 44 | // SecurityUtils.setSecurityManager(securityManager); 45 | Subject subject = SecurityUtils.getSubject(); 46 | subject.login(new UsernamePasswordToken(username,password)); 47 | 48 | SysUserEntity user = (SysUserEntity)subject.getPrincipal(); 49 | 50 | result.message = "登录成功~"; 51 | result.body = subject.getSession().getId(); 52 | return result; 53 | } 54 | 55 | @RequiresAuthentication 56 | public Result getUserInfo(JSONObject json){ 57 | Subject subject = SecurityUtils.getSubject(); 58 | // SysUserEntity user = (SysUserEntity)subject.getPrincipal(); 59 | 60 | JSONObject retJson = (JSONObject)JSON.toJSON(subject.getPrincipal()); 61 | retJson.remove("password"); 62 | retJson.remove("id"); 63 | retJson.remove("salt"); 64 | 65 | Result result = new Result(); 66 | result.body = retJson; 67 | return result; 68 | } 69 | 70 | @RequiresAuthentication 71 | public Result logout(JSONObject json){ 72 | Subject subject = SecurityUtils.getSubject(); 73 | subject.logout(); 74 | Result result = new Result(); 75 | result.message = "登出成功~"; 76 | return result; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.service; 2 | 3 | import com.example.springboot.dao.SysUserDao; 4 | import com.example.springboot.entity.SysUserEntity; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | @Component 13 | public class UserService { 14 | @Resource 15 | SysUserDao sysUserDao; 16 | 17 | public SysUserEntity findUserByName(String username){ 18 | SysUserEntity user = sysUserDao.findUniqueByProperty("username",username); 19 | if(user == null){ 20 | return null; 21 | } 22 | String sql = "select rolename " + 23 | "from sys_role r,sys_user_role ur,sys_user u " + 24 | "where r.id=ur.role_id and u.id=ur.user_id and u.id="+user.getId(); 25 | 26 | List roleList = sysUserDao.findListBySql(sql); 27 | 28 | 29 | sql = "select permission_code " + 30 | "from sys_role r,sys_user_role ur,sys_user u,sys_role_permission rp,sys_permission p " + 31 | "where r.id=ur.role_id and u.id=ur.user_id and rp.role_id=r.id and rp.permission_id=p.id and u.id="+user.getId(); 32 | 33 | List permissionList = sysUserDao.findListBySql(sql); 34 | 35 | sql = "select menu_path,menu_name " + 36 | "from sys_role r,sys_user_role ur,sys_user u,sys_role_menu rm,sys_menu m " + 37 | "where r.id=ur.role_id and u.id=ur.user_id and rm.role_id=r.id and rm.menu_id=m.id and u.id="+user.getId(); 38 | 39 | List> menuList = sysUserDao.findMapBySql(sql); 40 | 41 | user.setRoleList(roleList); 42 | user.setPermissionList(permissionList); 43 | user.setMenuList(menuList); 44 | return user; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/shiro/MyExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.shiro; 2 | 3 | import com.alibaba.fastjson.support.spring.FastJsonJsonView; 4 | import org.apache.shiro.authz.UnauthenticatedException; 5 | import org.apache.shiro.authz.UnauthorizedException; 6 | import org.springframework.web.servlet.HandlerExceptionResolver; 7 | import org.springframework.web.servlet.ModelAndView; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class MyExceptionHandler implements HandlerExceptionResolver { 15 | 16 | public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) { 17 | ModelAndView mv = new ModelAndView(); 18 | FastJsonJsonView view = new FastJsonJsonView(); 19 | Map attributes = new HashMap(); 20 | if (ex instanceof UnauthenticatedException) { 21 | attributes.put("code", "1000001"); 22 | attributes.put("msg", "token错误"); 23 | } else if (ex instanceof UnauthorizedException) { 24 | attributes.put("code", "1000002"); 25 | attributes.put("msg", "用户无权限"); 26 | } else { 27 | attributes.put("code", "1000003"); 28 | attributes.put("msg", ex.getMessage()); 29 | } 30 | 31 | view.setAttributesMap(attributes); 32 | mv.setView(view); 33 | return mv; 34 | }} 35 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/shiro/MyRedisSessionDAO.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.shiro; 2 | 3 | import com.example.springboot.util.RedisCacheManager; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.apache.shiro.session.Session; 7 | import org.apache.shiro.session.UnknownSessionException; 8 | import org.apache.shiro.session.mgt.SimpleSession; 9 | import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | 13 | import java.io.Serializable; 14 | import java.util.Collection; 15 | 16 | 17 | public class MyRedisSessionDAO extends AbstractSessionDAO { 18 | Log log = LogFactory.getLog(MyRedisSessionDAO.class); 19 | private final String REDIS_HASH = "SESSION"; 20 | 21 | @Autowired 22 | RedisCacheManager redisCacheManager; 23 | 24 | @Override 25 | protected Serializable doCreate(Session session) { 26 | Serializable id = super.generateSessionId(session); 27 | ((SimpleSession)session).setId(id); 28 | log.debug("doCreate session:" + id); 29 | redisCacheManager.hset(REDIS_HASH,id.toString(),session); 30 | return id; 31 | } 32 | 33 | @Override 34 | protected Session doReadSession(Serializable sessionId) { 35 | Session session = (Session)redisCacheManager.hget(REDIS_HASH,sessionId.toString()); 36 | log.debug("doReadSession session:" + sessionId); 37 | return session; 38 | } 39 | 40 | @Override 41 | public void update(Session session) throws UnknownSessionException { 42 | Serializable id = session.getId(); 43 | if(id == null){ 44 | throw new NullPointerException(); 45 | } 46 | redisCacheManager.hset(REDIS_HASH,id.toString(),session); 47 | 48 | } 49 | 50 | @Override 51 | public void delete(Session session) { 52 | Serializable id = session.getId(); 53 | redisCacheManager.hdel(REDIS_HASH,id.toString()); 54 | } 55 | 56 | @Override 57 | public Collection getActiveSessions() { 58 | return redisCacheManager.hgetAllValues(REDIS_HASH); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/shiro/MySessionManager.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.shiro; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.commons.logging.Log; 5 | import org.apache.commons.logging.LogFactory; 6 | import org.apache.shiro.session.Session; 7 | import org.apache.shiro.session.UnknownSessionException; 8 | import org.apache.shiro.session.mgt.SessionKey; 9 | import org.apache.shiro.web.servlet.ShiroHttpServletRequest; 10 | import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 11 | import org.apache.shiro.web.util.WebUtils; 12 | 13 | import javax.servlet.ServletRequest; 14 | import javax.servlet.ServletResponse; 15 | import java.io.Serializable; 16 | 17 | public class MySessionManager extends DefaultWebSessionManager { 18 | Log log = LogFactory.getLog(MyRedisSessionDAO.class); 19 | 20 | private static final String AUTHORIZATION = "X-Token"; 21 | 22 | private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; 23 | private static final String MY_SESSION_ATTRIBUTE = "MY_SESSION_ATTRIBUTE"; 24 | 25 | public MySessionManager() { 26 | super(); 27 | } 28 | 29 | @Override 30 | protected Serializable getSessionId(ServletRequest request, ServletResponse response) { 31 | String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION); 32 | //如果请求头中有 Authorization 则其值为sessionId 33 | if (!StringUtils.isEmpty(id)) { 34 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); 35 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); 36 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); 37 | return id; 38 | } else { 39 | //否则按默认规则从cookie取sessionId 40 | return super.getSessionId(request, response); 41 | } 42 | } 43 | 44 | @Override 45 | protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { 46 | Serializable sessionId = getSessionId(sessionKey); 47 | 48 | if (sessionId == null) { 49 | return null; 50 | } 51 | 52 | ServletRequest request = WebUtils.getRequest(sessionKey); 53 | 54 | if(request.getAttribute(MY_SESSION_ATTRIBUTE) != null){ 55 | log.debug("Get Session from request!"); 56 | return (Session)request.getAttribute(MY_SESSION_ATTRIBUTE); 57 | }else{ 58 | log.debug("Get Session from redis!"); 59 | Session s = retrieveSessionFromDataSource(sessionId); 60 | if (s == null) { 61 | //session ID was provided, meaning one is expected to be found, but we couldn't find one: 62 | String msg = "Could not find session with ID [" + sessionId + "]"; 63 | throw new UnknownSessionException(msg); 64 | } 65 | request.setAttribute(MY_SESSION_ATTRIBUTE,s); 66 | return s; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/shiro/MyShiroRealm.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.shiro; 2 | 3 | import com.example.springboot.dao.SysUserDao; 4 | import com.example.springboot.entity.SysUserEntity; 5 | import com.example.springboot.service.UserService; 6 | import org.apache.shiro.authc.*; 7 | import org.apache.shiro.authz.AuthorizationInfo; 8 | import org.apache.shiro.authz.SimpleAuthorizationInfo; 9 | import org.apache.shiro.realm.AuthorizingRealm; 10 | import org.apache.shiro.subject.PrincipalCollection; 11 | import org.apache.shiro.util.ByteSource; 12 | 13 | import javax.annotation.Resource; 14 | 15 | public class MyShiroRealm extends AuthorizingRealm { 16 | @Resource 17 | private UserService userService; 18 | 19 | @Override 20 | protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 21 | // System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); 22 | SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 23 | SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal(); 24 | for (String role : user.getRoleList()) { 25 | authorizationInfo.addRole(role); 26 | 27 | } 28 | for (String p : user.getPermissionList()) { 29 | authorizationInfo.addStringPermission(p); 30 | } 31 | return authorizationInfo; 32 | } 33 | 34 | /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/ 35 | @Override 36 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 37 | throws AuthenticationException { 38 | // System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); 39 | //获取用户的输入的账号. 40 | String username = (String) token.getPrincipal(); 41 | // System.out.println(token.getCredentials()); 42 | //通过username从数据库中查找 User对象,如果找到,没找到. 43 | //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 44 | SysUserEntity user = userService.findUserByName(username); 45 | // System.out.println("----->>userInfo="+userInfo); 46 | if (user == null) { 47 | return null; 48 | } 49 | if (user.getState() == SysUserEntity.STATE.INVALID) { //账户冻结 50 | throw new LockedAccountException(); 51 | } 52 | SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( 53 | user, //用户名 54 | user.getPassword(), //密码 55 | ByteSource.Util.bytes(user.getSalt()),// md5(salt+password),采用明文访问时,不需要此句 56 | getName() //realm name 57 | ); 58 | return authenticationInfo; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/shiro/ShiroConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.shiro; 2 | 3 | import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 4 | import org.apache.shiro.mgt.SecurityManager; 5 | import org.apache.shiro.session.mgt.SessionManager; 6 | import org.apache.shiro.session.mgt.eis.SessionDAO; 7 | import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 8 | import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 9 | import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 10 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 14 | import org.springframework.data.redis.core.RedisTemplate; 15 | import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; 16 | import org.springframework.data.redis.serializer.StringRedisSerializer; 17 | import org.springframework.web.filter.DelegatingFilterProxy; 18 | import org.springframework.web.servlet.HandlerExceptionResolver; 19 | 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | @Configuration 24 | public class ShiroConfig { 25 | 26 | /** 27 | * 自定义安全域,用户验证、权限等数据在此提供 28 | * @return 29 | */ 30 | @Bean 31 | public MyShiroRealm myShiroRealm() { 32 | MyShiroRealm myShiroRealm = new MyShiroRealm(); 33 | myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 34 | return myShiroRealm; 35 | } 36 | 37 | /** 38 | * 凭证匹配器,这里的hash算法用来将前台传入的数据做处理 39 | * 40 | * @return 41 | */ 42 | @Bean 43 | public HashedCredentialsMatcher hashedCredentialsMatcher() { 44 | HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 45 | hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; 46 | hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5("")); 47 | return hashedCredentialsMatcher; 48 | } 49 | 50 | /** 51 | * 注册shiro的filter,用于拦截所有请求 52 | * @return 53 | */ 54 | @Bean 55 | public FilterRegistrationBean delegatingFilterProxy(){ 56 | FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 57 | DelegatingFilterProxy proxy = new DelegatingFilterProxy(); 58 | proxy.setTargetFilterLifecycle(true); 59 | proxy.setTargetBeanName("shiroFilter"); 60 | filterRegistrationBean.setFilter(proxy); 61 | return filterRegistrationBean; 62 | } 63 | 64 | /** 65 | * 定义shiro的filter,进行基础配置 66 | * @param securityManager 67 | * @return 68 | */ 69 | @Bean("shiroFilter") 70 | public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { 71 | System.out.println("ShiroConfiguration.shirFilter()"); 72 | ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 73 | shiroFilterFactoryBean.setSecurityManager(securityManager); 74 | 75 | Map filterChainDefinitionMap = new LinkedHashMap(); 76 | //注意过滤器配置顺序 不能颠倒 77 | filterChainDefinitionMap.put("/", "anon"); 78 | filterChainDefinitionMap.put("/api", "anon"); 79 | filterChainDefinitionMap.put("/dist/static/**", "anon"); 80 | filterChainDefinitionMap.put("/**", "authc"); 81 | //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据 82 | shiroFilterFactoryBean.setLoginUrl("/unauth"); 83 | //下面的链接都不需要后台配置,返回相应数据后,前台自动路由 84 | // 登录成功后要跳转的链接 85 | //shiroFilterFactoryBean.setSuccessUrl("/index"); 86 | //未授权界面; 87 | // shiroFilterFactoryBean.setUnauthorizedUrl("/403"); 88 | shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 89 | return shiroFilterFactoryBean; 90 | } 91 | 92 | 93 | @Bean 94 | public SecurityManager securityManager() { 95 | DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 96 | securityManager.setRealm(myShiroRealm()); 97 | // 自定义session管理 使用redis 98 | securityManager.setSessionManager(sessionManager()); 99 | return securityManager; 100 | } 101 | 102 | /** 103 | * 自定义sessionManager,禁用cookie,使用http header方式传入sessionId 104 | * @return 105 | */ 106 | @Bean 107 | public SessionManager sessionManager() { 108 | MySessionManager mySessionManager = new MySessionManager(); 109 | mySessionManager.setSessionIdCookieEnabled(false); 110 | mySessionManager.setSessionDAO(sessionDAO()); 111 | return mySessionManager; 112 | } 113 | 114 | @Bean 115 | public RedisTemplate redisTemplateBean(LettuceConnectionFactory factory){ 116 | RedisTemplate template = new RedisTemplate(); 117 | template.setConnectionFactory(factory); 118 | template.setDefaultSerializer(new StringRedisSerializer()); 119 | template.setHashValueSerializer(new JdkSerializationRedisSerializer()); 120 | template.afterPropertiesSet(); 121 | return template; 122 | } 123 | 124 | @Bean 125 | public SessionDAO sessionDAO(){ 126 | return new MyRedisSessionDAO(); 127 | } 128 | 129 | 130 | /** 131 | * 开启shiro aop注解支持. 132 | * 使用代理方式;所以需要开启代码支持; 133 | * 134 | * @param securityManager 135 | * @return 136 | */ 137 | @Bean 138 | public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { 139 | AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 140 | authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 141 | return authorizationAttributeSourceAdvisor; 142 | } 143 | 144 | /** 145 | * 注册全局异常处理 146 | * @return 147 | */ 148 | @Bean(name = "exceptionHandler") 149 | public HandlerExceptionResolver handlerExceptionResolver() { 150 | return new MyExceptionHandler(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/util/JSONUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.alibaba.fastjson.serializer.SerializerFeature; 6 | 7 | /** 8 | * Created by wangfeng on 17/1/9. 9 | */ 10 | public class JSONUtil { 11 | public static String toJson(Object o){ 12 | return JSON.toJSONString(o, SerializerFeature.WriteDateUseDateFormat); 13 | } 14 | 15 | public static JSONObject fromJson(String json){ 16 | return JSON.parseObject(json); 17 | } 18 | 19 | public static JSONObject fromObject(Object o){ 20 | return JSON.parseObject(toJson(o)); 21 | } 22 | 23 | public static T toObject(String json,Class clazz){ 24 | return JSON.parseObject(json,clazz); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/util/SpringContextHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * @(#)SpringContextHolder.java 2013-11-7下午06:10:02 3 | * Copyright 2013 sinovatech, Inc. All rights reserved. 4 | */ 5 | package com.example.springboot.util; 6 | 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.ApplicationContextAware; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * Spring上下文持有类 14 | * @modificationHistory. 15 | * 16 | * sunju 2013-11-7下午06:10:02 TODO 17 | * 18 | */ 19 | @SuppressWarnings("unchecked") 20 | @Component 21 | public class SpringContextHolder implements ApplicationContextAware { 22 | /** 23 | * 以静态变量保存Spring ApplicationContext,可在任何代码任何地方任何时候中取出ApplicaitonContext. 24 | */ 25 | private static ApplicationContext applicationContext; 26 | 27 | /* (non-Javadoc) 28 | * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) 29 | */ 30 | public void setApplicationContext(ApplicationContext context) 31 | throws BeansException { 32 | // TODO Auto-generated method stub 33 | SpringContextHolder.applicationContext = context; 34 | } 35 | /** 36 | * 取得存储在静态变量中的ApplicationContext 37 | * @author sunju 38 | * @creationDate. 2013-11-7 下午06:16:33 39 | * @return 40 | */ 41 | public static ApplicationContext getApplicationContext() { 42 | checkApplicationContext(); 43 | return applicationContext; 44 | } 45 | /** 46 | * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型 47 | * @author sunju 48 | * @creationDate. 2013-11-7 下午06:16:40 49 | * @param 50 | * @param name 51 | * @return 52 | */ 53 | public static T getBean(String name) { 54 | checkApplicationContext(); 55 | return (T) applicationContext.getBean(name); 56 | } 57 | 58 | /** 59 | * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型 60 | * @author sunju 61 | * @creationDate. 2013-11-7 下午06:17:01 62 | * @param 63 | * @param clazz 64 | * @return 65 | */ 66 | public static T getBean(Class clazz) { 67 | checkApplicationContext(); 68 | return (T) applicationContext.getBeansOfType(clazz); 69 | } 70 | 71 | /** 72 | * 清除applicationContext静态变量 73 | * @author sunju 74 | * @creationDate. 2013-11-7 下午06:18:12 75 | */ 76 | public static void cleanApplicationContext() { 77 | applicationContext = null; 78 | } 79 | 80 | /** 81 | * 检查注入 82 | * @author sunju 83 | * @creationDate. 2013-11-7 下午06:17:27 84 | */ 85 | private static void checkApplicationContext() { 86 | if (applicationContext == null) { 87 | throw new IllegalStateException("applicaitonContext未注入,请在xml中定义SpringContextHolder"); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/example/springboot/util/ValidateUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | public class ValidateUtil { 6 | public static void validJSONParam(JSONObject json,String... keys){ 7 | for(String key :keys){ 8 | if(!json.containsKey(key)){ 9 | throw new RuntimeException("参数错误"); 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true&useTimezone=true&serverTimezone=GMT%2B8&useLegacyDatetimeCode=false 4 | username: root 5 | password: 123456 6 | jpa: 7 | database: mysql 8 | show-sql: true 9 | hibernate: 10 | ddl-auto: none 11 | naming: 12 | physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 13 | redis: 14 | host: 127.0.0.1 15 | port: 6379 16 | logging: 17 | level: 18 | com.example.springboot: trace 19 | org.apache.shiro: trace 20 | server: 21 | tomcat: 22 | max-threads: 1 23 | -------------------------------------------------------------------------------- /src/main/resources/mybatis-generator/generatorConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/mybatis-generator/mybatisGeneratorinit.properties: -------------------------------------------------------------------------------- 1 | #Mybatis Generator configuration 2 | #dao类和实体类的位置 3 | project =src/main/java 4 | #mapper文件的位置 5 | resources=src/main/java 6 | #根据数据库中的表生成对应的pojo类、dao、mapper 7 | jdbc_driver =com.mysql.jdbc.Driver 8 | jdbc_url=jdbc:mysql://localhost:3306/test 9 | jdbc_user=root 10 | jdbc_password=123456 -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fwing1987/SpringBoot-Shiro-Vue/554b8392185c7ecb9ca56ba42da0ae0a30d32738/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/test/java/com/example/springboot/SpringbootApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.springboot; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringbootApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/vue-admin-template/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins":["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /src/vue-admin-template/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/vue-admin-template/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/assets 4 | -------------------------------------------------------------------------------- /src/vue-admin-template/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true, 10 | es6: true, 11 | }, 12 | extends: ['plugin:vue/recommended', 'eslint:recommended'], 13 | 14 | // add your custom rules here 15 | //it is base on https://github.com/vuejs/eslint-config-vue 16 | rules: { 17 | "vue/max-attributes-per-line": [2, { 18 | "singleline": 10, 19 | "multiline": { 20 | "max": 1, 21 | "allowFirstLine": false 22 | } 23 | }], 24 | "vue/name-property-casing": ["error", "PascalCase"], 25 | 'accessor-pairs': 2, 26 | 'arrow-spacing': [2, { 27 | 'before': true, 28 | 'after': true 29 | }], 30 | 'block-spacing': [2, 'always'], 31 | 'brace-style': [2, '1tbs', { 32 | 'allowSingleLine': true 33 | }], 34 | 'camelcase': [0, { 35 | 'properties': 'always' 36 | }], 37 | 'comma-dangle': [2, 'never'], 38 | 'comma-spacing': [2, { 39 | 'before': false, 40 | 'after': true 41 | }], 42 | 'comma-style': [2, 'last'], 43 | 'constructor-super': 2, 44 | 'curly': [2, 'multi-line'], 45 | 'dot-location': [2, 'property'], 46 | 'eol-last': 2, 47 | 'eqeqeq': [2, 'allow-null'], 48 | 'generator-star-spacing': [2, { 49 | 'before': true, 50 | 'after': true 51 | }], 52 | 'handle-callback-err': [2, '^(err|error)$'], 53 | 'indent': [2, 2, { 54 | 'SwitchCase': 1 55 | }], 56 | 'jsx-quotes': [2, 'prefer-single'], 57 | 'key-spacing': [2, { 58 | 'beforeColon': false, 59 | 'afterColon': true 60 | }], 61 | 'keyword-spacing': [2, { 62 | 'before': true, 63 | 'after': true 64 | }], 65 | 'new-cap': [2, { 66 | 'newIsCap': true, 67 | 'capIsNew': false 68 | }], 69 | 'new-parens': 2, 70 | 'no-array-constructor': 2, 71 | 'no-caller': 2, 72 | 'no-console': 'off', 73 | 'no-class-assign': 2, 74 | 'no-cond-assign': 2, 75 | 'no-const-assign': 2, 76 | 'no-control-regex': 2, 77 | 'no-delete-var': 2, 78 | 'no-dupe-args': 2, 79 | 'no-dupe-class-members': 2, 80 | 'no-dupe-keys': 2, 81 | 'no-duplicate-case': 2, 82 | 'no-empty-character-class': 2, 83 | 'no-empty-pattern': 2, 84 | 'no-eval': 2, 85 | 'no-ex-assign': 2, 86 | 'no-extend-native': 2, 87 | 'no-extra-bind': 2, 88 | 'no-extra-boolean-cast': 2, 89 | 'no-extra-parens': [2, 'functions'], 90 | 'no-fallthrough': 2, 91 | 'no-floating-decimal': 2, 92 | 'no-func-assign': 2, 93 | 'no-implied-eval': 2, 94 | 'no-inner-declarations': [2, 'functions'], 95 | 'no-invalid-regexp': 2, 96 | 'no-irregular-whitespace': 2, 97 | 'no-iterator': 2, 98 | 'no-label-var': 2, 99 | 'no-labels': [2, { 100 | 'allowLoop': false, 101 | 'allowSwitch': false 102 | }], 103 | 'no-lone-blocks': 2, 104 | 'no-mixed-spaces-and-tabs': 2, 105 | 'no-multi-spaces': 2, 106 | 'no-multi-str': 2, 107 | 'no-multiple-empty-lines': [2, { 108 | 'max': 1 109 | }], 110 | 'no-native-reassign': 2, 111 | 'no-negated-in-lhs': 2, 112 | 'no-new-object': 2, 113 | 'no-new-require': 2, 114 | 'no-new-symbol': 2, 115 | 'no-new-wrappers': 2, 116 | 'no-obj-calls': 2, 117 | 'no-octal': 2, 118 | 'no-octal-escape': 2, 119 | 'no-path-concat': 2, 120 | 'no-proto': 2, 121 | 'no-redeclare': 2, 122 | 'no-regex-spaces': 2, 123 | 'no-return-assign': [2, 'except-parens'], 124 | 'no-self-assign': 2, 125 | 'no-self-compare': 2, 126 | 'no-sequences': 2, 127 | 'no-shadow-restricted-names': 2, 128 | 'no-spaced-func': 2, 129 | 'no-sparse-arrays': 2, 130 | 'no-this-before-super': 2, 131 | 'no-throw-literal': 2, 132 | 'no-trailing-spaces': 2, 133 | 'no-undef': 2, 134 | 'no-undef-init': 2, 135 | 'no-unexpected-multiline': 2, 136 | 'no-unmodified-loop-condition': 2, 137 | 'no-unneeded-ternary': [2, { 138 | 'defaultAssignment': false 139 | }], 140 | 'no-unreachable': 2, 141 | 'no-unsafe-finally': 2, 142 | 'no-unused-vars': [2, { 143 | 'vars': 'all', 144 | 'args': 'none' 145 | }], 146 | 'no-useless-call': 2, 147 | 'no-useless-computed-key': 2, 148 | 'no-useless-constructor': 2, 149 | 'no-useless-escape': 0, 150 | 'no-whitespace-before-property': 2, 151 | 'no-with': 2, 152 | 'one-var': [2, { 153 | 'initialized': 'never' 154 | }], 155 | 'operator-linebreak': [2, 'after', { 156 | 'overrides': { 157 | '?': 'before', 158 | ':': 'before' 159 | } 160 | }], 161 | 'padded-blocks': [2, 'never'], 162 | 'quotes': [2, 'single', { 163 | 'avoidEscape': true, 164 | 'allowTemplateLiterals': true 165 | }], 166 | 'semi': [2, 'never'], 167 | 'semi-spacing': [2, { 168 | 'before': false, 169 | 'after': true 170 | }], 171 | 'space-before-blocks': [2, 'always'], 172 | 'space-before-function-paren': [2, 'never'], 173 | 'space-in-parens': [2, 'never'], 174 | 'space-infix-ops': 2, 175 | 'space-unary-ops': [2, { 176 | 'words': true, 177 | 'nonwords': false 178 | }], 179 | 'spaced-comment': [2, 'always', { 180 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] 181 | }], 182 | 'template-curly-spacing': [2, 'never'], 183 | 'use-isnan': 2, 184 | 'valid-typeof': 2, 185 | 'wrap-iife': [2, 'any'], 186 | 'yield-star-spacing': [2, 'both'], 187 | 'yoda': [2, 'never'], 188 | 'prefer-const': 2, 189 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 190 | 'object-curly-spacing': [2, 'always', { 191 | objectsInObjects: false 192 | }], 193 | 'array-bracket-spacing': [2, 'never'] 194 | } 195 | } 196 | 197 | -------------------------------------------------------------------------------- /src/vue-admin-template/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | -------------------------------------------------------------------------------- /src/vue-admin-template/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/vue-admin-template/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /src/vue-admin-template/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/vue-admin-template/README-zh.md: -------------------------------------------------------------------------------- 1 | # vue-admin-template 2 | 3 | > 这是一个 极简的 vue admin 管理后台 它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。 4 | 5 | [线上地址](http://panjiachen.github.io/vue-admin-template) 6 | 7 | [国内访问](https://panjiachen.gitee.io/vue-admin-template) 8 | 9 | ## Extra 10 | 11 | 如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用改分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) 12 | 13 | 本项目基于`webpack4`开发,若还想使用`webpack3`开发,请使用该分支[webpack3](https://github.com/PanJiaChen/vue-admin-template/tree/webpack3) 14 | 15 | 如果你想使用基于 vue + typescript 的管理后台, 可以看看这个项目: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (鸣谢: [@Armour](https://github.com/Armour)) 16 | 17 | ## 相关项目 18 | 19 | [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 20 | 21 | [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) 22 | 23 | [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) 24 | 25 | 写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: 26 | 27 | - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) 28 | - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) 29 | - [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) 30 | - [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56) 31 | - [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836) 32 | 33 | ## Build Setup 34 | 35 | ```bash 36 | # Clone project 37 | git clone https://github.com/PanJiaChen/vue-admin-template.git 38 | 39 | # Install dependencies 40 | npm install 41 | 42 | # 建议不要用cnpm 安装有各种诡异的bug 可以通过如下操作解决npm速度慢的问题 43 | npm install --registry=https://registry.npm.taobao.org 44 | 45 | # Serve with hot reload at localhost:9528 46 | npm run dev 47 | 48 | # Build for production with minification 49 | npm run build 50 | 51 | # Build for production and view the bundle analyzer report 52 | npm run build --report 53 | ``` 54 | 55 | ## Demo 56 | 57 |  58 | 59 | ### Element-Ui 使用 cdn 教程 60 | 61 | 首先找到 `index.html` ([根目录下](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/index.html)) 62 | 63 | 引入 Element 的 css 和 js ,并且引入 vue 。因为 Element-Ui 是依赖 vue 的,所以必须在它之前引入 vue 。 64 | 65 | 之后找到 [webpack.base.conf.js](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/build/webpack.base.conf.js) 加入 `externals` 让 webpack 不打包 vue 和 element 66 | 67 | ``` 68 | externals: { 69 | vue: 'Vue', 70 | 'element-ui':'ELEMENT' 71 | } 72 | ``` 73 | 74 | 之后还有一个小细节是如果你用了全局对象方式引入 vue,就不需要 手动 `Vue.use(Vuex)` ,它会自动挂载,具体见 [issue](https://github.com/vuejs/vuex/issues/731) 75 | 76 | 最终你可以使用 `npm run build --report` 查看效果 77 | 如图: 78 |  79 | 80 | **[具体代码](https://github.com/PanJiaChen/vue-admin-template/commit/746aff560932704ae821f82f10b8b2a9681d5177)** 81 | 82 | **[对应分支](https://github.com/PanJiaChen/vue-admin-template/tree/element-ui-cdn)** 83 | 84 | ## Browsers support 85 | 86 | Modern browsers and Internet Explorer 10+. 87 | 88 | | [](http://godban.github.io/browsers-support-badges/)IE / Edge | [](http://godban.github.io/browsers-support-badges/)Firefox | [](http://godban.github.io/browsers-support-badges/)Chrome | [](http://godban.github.io/browsers-support-badges/)Safari | 89 | | --------- | --------- | --------- | --------- | 90 | | IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions 91 | 92 | ## License 93 | 94 | [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. 95 | 96 | Copyright (c) 2017-present PanJiaChen 97 | -------------------------------------------------------------------------------- /src/vue-admin-template/README.md: -------------------------------------------------------------------------------- 1 | # vue-admin-template 2 | 3 | > A minimal vue admin template with Element UI & axios & iconfont & permission control & lint 4 | 5 | **Live demo:** http://panjiachen.github.io/vue-admin-template 6 | 7 | [中文文档](https://github.com/PanJiaChen/vue-admin-template/blob/master/README-zh.md) 8 | 9 | ## Build Setup 10 | 11 | ```bash 12 | # Clone project 13 | git clone https://github.com/PanJiaChen/vue-admin-template.git 14 | 15 | # Install dependencies 16 | npm install 17 | 18 | # Serve with hot reload at localhost:9528 19 | npm run dev 20 | 21 | # Build for production with minification 22 | npm run build 23 | 24 | # Build for production and view the bundle analyzer report 25 | npm run build --report 26 | ``` 27 | 28 | ## Demo 29 | 30 |  31 | 32 | ## Extra 33 | 34 | If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) 35 | 36 | This project is based on `webpack4` development. If you want to use `webpack3` development, please use this branch [webpack3](https://github.com/PanJiaChen/vue-admin-template/tree/webpack3) 37 | 38 | For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour)) 39 | 40 | ## Related Project 41 | 42 | [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 43 | 44 | [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) 45 | 46 | [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) 47 | 48 | ### Element-Ui using cdn tutorial 49 | 50 | First find `index.html`([root directory](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/index.html)) 51 | 52 | Import css and js of `Element`, and then import vue. Because `Element` is vue-dependent, vue must be import before it. 53 | 54 | Then find [webpack.base.conf.js](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/build/webpack.base.conf.js) 55 | Add `externals` to make webpack not package vue and element. 56 | 57 | ``` 58 | externals: { 59 | vue: 'Vue', 60 | 'element-ui':'ELEMENT' 61 | } 62 | ``` 63 | 64 | Finally there is a small detail to pay attention to that if you import vue in global, you don't need to manually `Vue.use(Vuex)`, it will be automatically mounted, see 65 | [issue](https://github.com/vuejs/vuex/issues/731) 66 | 67 | And you can use `npm run build --report` to see the effect 68 | 69 | Pictured: 70 |  71 | 72 | **[Detailed code](https://github.com/PanJiaChen/vue-admin-template/commit/746aff560932704ae821f82f10b8b2a9681d5177)** 73 | 74 | **[Branch](https://github.com/PanJiaChen/vue-admin-template/tree/element-ui-cdn)** 75 | 76 | ## Browsers support 77 | 78 | Modern browsers and Internet Explorer 10+. 79 | 80 | | [](http://godban.github.io/browsers-support-badges/)IE / Edge | [](http://godban.github.io/browsers-support-badges/)Firefox | [](http://godban.github.io/browsers-support-badges/)Chrome | [](http://godban.github.io/browsers-support-badges/)Safari | 81 | | --------- | --------- | --------- | --------- | 82 | | IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions 83 | 84 | ## License 85 | 86 | [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. 87 | 88 | Copyright (c) 2017-present PanJiaChen 89 | -------------------------------------------------------------------------------- /src/vue-admin-template/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write( 23 | stats.toString({ 24 | colors: true, 25 | modules: false, 26 | children: false, 27 | chunks: false, 28 | chunkModules: false 29 | }) + '\n\n' 30 | ) 31 | 32 | if (stats.hasErrors()) { 33 | console.log(chalk.red(' Build failed with errors.\n')) 34 | process.exit(1) 35 | } 36 | 37 | console.log(chalk.cyan(' Build complete.\n')) 38 | console.log( 39 | chalk.yellow( 40 | ' Tip: built files are meant to be served over an HTTP server.\n' + 41 | " Opening index.html over file:// won't work.\n" 42 | ) 43 | ) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /src/vue-admin-template/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec(cmd) { 8 | return require('child_process') 9 | .execSync(cmd) 10 | .toString() 11 | .trim() 12 | } 13 | 14 | const versionRequirements = [ 15 | { 16 | name: 'node', 17 | currentVersion: semver.clean(process.version), 18 | versionRequirement: packageConfig.engines.node 19 | } 20 | ] 21 | 22 | if (shell.which('npm')) { 23 | versionRequirements.push({ 24 | name: 'npm', 25 | currentVersion: exec('npm --version'), 26 | versionRequirement: packageConfig.engines.npm 27 | }) 28 | } 29 | 30 | module.exports = function() { 31 | const warnings = [] 32 | 33 | for (let i = 0; i < versionRequirements.length; i++) { 34 | const mod = versionRequirements[i] 35 | 36 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 37 | warnings.push( 38 | mod.name + 39 | ': ' + 40 | chalk.red(mod.currentVersion) + 41 | ' should be ' + 42 | chalk.green(mod.versionRequirement) 43 | ) 44 | } 45 | } 46 | 47 | if (warnings.length) { 48 | console.log('') 49 | console.log( 50 | chalk.yellow( 51 | 'To use this template, you must update following to modules:' 52 | ) 53 | ) 54 | console.log() 55 | 56 | for (let i = 0; i < warnings.length; i++) { 57 | const warning = warnings[i] 58 | console.log(' ' + warning) 59 | } 60 | 61 | console.log() 62 | process.exit(1) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/vue-admin-template/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fwing1987/SpringBoot-Shiro-Vue/554b8392185c7ecb9ca56ba42da0ae0a30d32738/src/vue-admin-template/build/logo.png -------------------------------------------------------------------------------- /src/vue-admin-template/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function(_path) { 8 | const assetsSubDirectory = 9 | process.env.NODE_ENV === 'production' 10 | ? config.build.assetsSubDirectory 11 | : config.dev.assetsSubDirectory 12 | 13 | return path.posix.join(assetsSubDirectory, _path) 14 | } 15 | 16 | exports.cssLoaders = function(options) { 17 | options = options || {} 18 | 19 | const cssLoader = { 20 | loader: 'css-loader', 21 | options: { 22 | sourceMap: options.sourceMap 23 | } 24 | } 25 | 26 | const postcssLoader = { 27 | loader: 'postcss-loader', 28 | options: { 29 | sourceMap: options.sourceMap 30 | } 31 | } 32 | 33 | // generate loader string to be used with extract text plugin 34 | function generateLoaders(loader, loaderOptions) { 35 | const loaders = [] 36 | 37 | // Extract CSS when that option is specified 38 | // (which is the case during production build) 39 | if (options.extract) { 40 | loaders.push(MiniCssExtractPlugin.loader) 41 | } else { 42 | loaders.push('vue-style-loader') 43 | } 44 | 45 | loaders.push(cssLoader) 46 | 47 | if (options.usePostCSS) { 48 | loaders.push(postcssLoader) 49 | } 50 | 51 | if (loader) { 52 | loaders.push({ 53 | loader: loader + '-loader', 54 | options: Object.assign({}, loaderOptions, { 55 | sourceMap: options.sourceMap 56 | }) 57 | }) 58 | } 59 | 60 | return loaders 61 | } 62 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 63 | return { 64 | css: generateLoaders(), 65 | postcss: generateLoaders(), 66 | less: generateLoaders('less'), 67 | sass: generateLoaders('sass', { 68 | indentedSyntax: true 69 | }), 70 | scss: generateLoaders('sass'), 71 | stylus: generateLoaders('stylus'), 72 | styl: generateLoaders('stylus') 73 | } 74 | } 75 | 76 | // Generate loaders for standalone style files (outside of .vue) 77 | exports.styleLoaders = function(options) { 78 | const output = [] 79 | const loaders = exports.cssLoaders(options) 80 | 81 | for (const extension in loaders) { 82 | const loader = loaders[extension] 83 | output.push({ 84 | test: new RegExp('\\.' + extension + '$'), 85 | use: loader 86 | }) 87 | } 88 | 89 | return output 90 | } 91 | 92 | exports.createNotifierCallback = () => { 93 | const notifier = require('node-notifier') 94 | 95 | return (severity, errors) => { 96 | if (severity !== 'error') return 97 | 98 | const error = errors[0] 99 | const filename = error.file && error.file.split('!').pop() 100 | 101 | notifier.notify({ 102 | title: packageConfig.name, 103 | message: severity + ': ' + error.name, 104 | subtitle: filename || '', 105 | icon: path.join(__dirname, 'logo.png') 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/vue-admin-template/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | //You can set the vue-loader configuration by yourself. 5 | } 6 | -------------------------------------------------------------------------------- /src/vue-admin-template/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const { VueLoaderPlugin } = require('vue-loader') 6 | const vueLoaderConfig = require('./vue-loader.conf') 7 | 8 | function resolve(dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | const createLintingRule = () => ({ 13 | test: /\.(js|vue)$/, 14 | loader: 'eslint-loader', 15 | enforce: 'pre', 16 | include: [resolve('src'), resolve('test')], 17 | options: { 18 | formatter: require('eslint-friendly-formatter'), 19 | emitWarning: !config.dev.showEslintErrorsInOverlay 20 | } 21 | }) 22 | 23 | module.exports = { 24 | context: path.resolve(__dirname, '../'), 25 | entry: { 26 | app: './src/main.js' 27 | }, 28 | output: { 29 | path: config.build.assetsRoot, 30 | filename: '[name].js', 31 | publicPath: 32 | process.env.NODE_ENV === 'production' 33 | ? config.build.assetsPublicPath 34 | : config.dev.assetsPublicPath 35 | }, 36 | resolve: { 37 | extensions: ['.js', '.vue', '.json'], 38 | alias: { 39 | '@': resolve('src') 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | ...(config.dev.useEslint ? [createLintingRule()] : []), 45 | { 46 | test: /\.vue$/, 47 | loader: 'vue-loader', 48 | options: vueLoaderConfig 49 | }, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel-loader', 53 | include: [ 54 | resolve('src'), 55 | resolve('test'), 56 | resolve('node_modules/webpack-dev-server/client') 57 | ] 58 | }, 59 | { 60 | test: /\.svg$/, 61 | loader: 'svg-sprite-loader', 62 | include: [resolve('src/icons')], 63 | options: { 64 | symbolId: 'icon-[name]' 65 | } 66 | }, 67 | { 68 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 69 | loader: 'url-loader', 70 | exclude: [resolve('src/icons')], 71 | options: { 72 | limit: 10000, 73 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 74 | } 75 | }, 76 | { 77 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 78 | loader: 'url-loader', 79 | options: { 80 | limit: 10000, 81 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 82 | } 83 | }, 84 | { 85 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 86 | loader: 'url-loader', 87 | options: { 88 | limit: 10000, 89 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 90 | } 91 | } 92 | ] 93 | }, 94 | plugins: [new VueLoaderPlugin()], 95 | node: { 96 | // prevent webpack from injecting useless setImmediate polyfill because Vue 97 | // source contains it (although only uses it if it's native). 98 | setImmediate: false, 99 | // prevent webpack from injecting mocks to Node native modules 100 | // that does not make sense for the client 101 | dgram: 'empty', 102 | fs: 'empty', 103 | net: 'empty', 104 | tls: 'empty', 105 | child_process: 'empty' 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/vue-admin-template/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const HtmlWebpackPlugin = require('html-webpack-plugin') 9 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 10 | const portfinder = require('portfinder') 11 | 12 | function resolve(dir) { 13 | return path.join(__dirname, '..', dir) 14 | } 15 | 16 | const HOST = process.env.HOST 17 | const PORT = process.env.PORT && Number(process.env.PORT) 18 | 19 | const devWebpackConfig = merge(baseWebpackConfig, { 20 | mode: 'development', 21 | module: { 22 | rules: utils.styleLoaders({ 23 | sourceMap: config.dev.cssSourceMap, 24 | usePostCSS: true 25 | }) 26 | }, 27 | // cheap-module-eval-source-map is faster for development 28 | devtool: config.dev.devtool, 29 | 30 | // these devServer options should be customized in /config/index.js 31 | devServer: { 32 | clientLogLevel: 'warning', 33 | historyApiFallback: true, 34 | hot: true, 35 | compress: true, 36 | host: HOST || config.dev.host, 37 | port: PORT || config.dev.port, 38 | open: config.dev.autoOpenBrowser, 39 | overlay: config.dev.errorOverlay 40 | ? { warnings: false, errors: true } 41 | : false, 42 | publicPath: config.dev.assetsPublicPath, 43 | proxy: config.dev.proxyTable, 44 | quiet: true, // necessary for FriendlyErrorsPlugin 45 | watchOptions: { 46 | poll: config.dev.poll 47 | } 48 | }, 49 | plugins: [ 50 | new webpack.DefinePlugin({ 51 | 'process.env': require('../config/dev.env') 52 | }), 53 | new webpack.HotModuleReplacementPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true, 59 | favicon: resolve('favicon.ico'), 60 | title: 'vue-admin-template' 61 | }) 62 | ] 63 | }) 64 | 65 | module.exports = new Promise((resolve, reject) => { 66 | portfinder.basePort = process.env.PORT || config.dev.port 67 | portfinder.getPort((err, port) => { 68 | if (err) { 69 | reject(err) 70 | } else { 71 | // publish the new Port, necessary for e2e tests 72 | process.env.PORT = port 73 | // add port to devServer config 74 | devWebpackConfig.devServer.port = port 75 | 76 | // Add FriendlyErrorsPlugin 77 | devWebpackConfig.plugins.push( 78 | new FriendlyErrorsPlugin({ 79 | compilationSuccessInfo: { 80 | messages: [ 81 | `Your application is running here: http://${ 82 | devWebpackConfig.devServer.host 83 | }:${port}` 84 | ] 85 | }, 86 | onErrors: config.dev.notifyOnErrors 87 | ? utils.createNotifierCallback() 88 | : undefined 89 | }) 90 | ) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /src/vue-admin-template/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin') 11 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 12 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') 13 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 14 | 15 | function resolve(dir) { 16 | return path.join(__dirname, '..', dir) 17 | } 18 | 19 | const env = require('../config/prod.env') 20 | 21 | // For NamedChunksPlugin 22 | const seen = new Set() 23 | const nameLength = 4 24 | 25 | const webpackConfig = merge(baseWebpackConfig, { 26 | mode: 'production', 27 | module: { 28 | rules: utils.styleLoaders({ 29 | sourceMap: config.build.productionSourceMap, 30 | extract: true, 31 | usePostCSS: true 32 | }) 33 | }, 34 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 35 | output: { 36 | path: config.build.assetsRoot, 37 | filename: utils.assetsPath('js/[name].[chunkhash:8].js'), 38 | chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js') 39 | }, 40 | plugins: [ 41 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 42 | new webpack.DefinePlugin({ 43 | 'process.env': env 44 | }), 45 | // extract css into its own file 46 | new MiniCssExtractPlugin({ 47 | filename: utils.assetsPath('css/[name].[contenthash:8].css'), 48 | chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css') 49 | }), 50 | // generate dist index.html with correct asset hash for caching. 51 | // you can customize output by editing /index.html 52 | // see https://github.com/ampedandwired/html-webpack-plugin 53 | new HtmlWebpackPlugin({ 54 | filename: config.build.index, 55 | template: 'index.html', 56 | inject: true, 57 | favicon: resolve('favicon.ico'), 58 | title: 'vue-admin-template', 59 | minify: { 60 | removeComments: true, 61 | collapseWhitespace: true, 62 | removeAttributeQuotes: true 63 | // more options: 64 | // https://github.com/kangax/html-minifier#options-quick-reference 65 | } 66 | // default sort mode uses toposort which cannot handle cyclic deps 67 | // in certain cases, and in webpack 4, chunk order in HTML doesn't 68 | // matter anyway 69 | }), 70 | new ScriptExtHtmlWebpackPlugin({ 71 | //`runtime` must same as runtimeChunk name. default is `runtime` 72 | inline: /runtime\..*\.js$/ 73 | }), 74 | // keep chunk.id stable when chunk has no name 75 | new webpack.NamedChunksPlugin(chunk => { 76 | if (chunk.name) { 77 | return chunk.name 78 | } 79 | const modules = Array.from(chunk.modulesIterable) 80 | if (modules.length > 1) { 81 | const hash = require('hash-sum') 82 | const joinedHash = hash(modules.map(m => m.id).join('_')) 83 | let len = nameLength 84 | while (seen.has(joinedHash.substr(0, len))) len++ 85 | seen.add(joinedHash.substr(0, len)) 86 | return `chunk-${joinedHash.substr(0, len)}` 87 | } else { 88 | return modules[0].id 89 | } 90 | }), 91 | // keep module.id stable when vender modules does not change 92 | new webpack.HashedModuleIdsPlugin(), 93 | // copy custom static assets 94 | new CopyWebpackPlugin([ 95 | { 96 | from: path.resolve(__dirname, '../static'), 97 | to: config.build.assetsSubDirectory, 98 | ignore: ['.*'] 99 | } 100 | ]) 101 | ], 102 | optimization: { 103 | splitChunks: { 104 | chunks: 'all', 105 | cacheGroups: { 106 | libs: { 107 | name: 'chunk-libs', 108 | test: /[\\/]node_modules[\\/]/, 109 | priority: 10, 110 | chunks: 'initial' // 只打包初始时依赖的第三方 111 | }, 112 | elementUI: { 113 | name: 'chunk-elementUI', // 单独将 elementUI 拆包 114 | priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app 115 | test: /[\\/]node_modules[\\/]element-ui[\\/]/ 116 | } 117 | } 118 | }, 119 | runtimeChunk: 'single', 120 | minimizer: [ 121 | new UglifyJsPlugin({ 122 | uglifyOptions: { 123 | mangle: { 124 | safari10: true 125 | } 126 | }, 127 | sourceMap: config.build.productionSourceMap, 128 | cache: true, 129 | parallel: true 130 | }), 131 | // Compress extracted CSS. We are using this plugin so that possible 132 | // duplicated CSS from different components can be deduped. 133 | new OptimizeCSSAssetsPlugin() 134 | ] 135 | } 136 | }) 137 | 138 | if (config.build.productionGzip) { 139 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 140 | 141 | webpackConfig.plugins.push( 142 | new CompressionWebpackPlugin({ 143 | asset: '[path].gz[query]', 144 | algorithm: 'gzip', 145 | test: new RegExp( 146 | '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' 147 | ), 148 | threshold: 10240, 149 | minRatio: 0.8 150 | }) 151 | ) 152 | } 153 | 154 | if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) { 155 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') 156 | .BundleAnalyzerPlugin 157 | 158 | if (config.build.bundleAnalyzerReport) { 159 | webpackConfig.plugins.push( 160 | new BundleAnalyzerPlugin({ 161 | analyzerPort: 8080, 162 | generateStatsFile: false 163 | }) 164 | ) 165 | } 166 | 167 | if (config.build.generateAnalyzerReport) { 168 | webpackConfig.plugins.push( 169 | new BundleAnalyzerPlugin({ 170 | analyzerMode: 'static', 171 | reportFilename: 'bundle-report.html', 172 | openAnalyzer: false 173 | }) 174 | ) 175 | } 176 | } 177 | 178 | module.exports = webpackConfig 179 | -------------------------------------------------------------------------------- /src/vue-admin-template/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | BASE_API: '"http://localhost:8080/api"', 8 | }) 9 | -------------------------------------------------------------------------------- /src/vue-admin-template/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.6 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | // Paths 10 | assetsSubDirectory: 'static', 11 | assetsPublicPath: '/', 12 | proxyTable: {}, 13 | 14 | // Various Dev Server settings 15 | host: 'localhost', // can be overwritten by process.env.HOST 16 | port: 9528, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 17 | autoOpenBrowser: true, 18 | errorOverlay: true, 19 | notifyOnErrors: false, 20 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 21 | 22 | // Use Eslint Loader? 23 | // If true, your code will be linted during bundling and 24 | // linting errors and warnings will be shown in the console. 25 | useEslint: true, 26 | // If true, eslint errors and warnings will also be shown in the error overlay 27 | // in the browser. 28 | showEslintErrorsInOverlay: false, 29 | 30 | /** 31 | * Source Maps 32 | */ 33 | 34 | // https://webpack.js.org/configuration/devtool/#development 35 | devtool: 'cheap-source-map', 36 | 37 | // CSS Sourcemaps off by default because relative paths are "buggy" 38 | // with this option, according to the CSS-Loader README 39 | // (https://github.com/webpack/css-loader#sourcemaps) 40 | // In our experience, they generally work as expected, 41 | // just be aware of this issue when enabling this option. 42 | cssSourceMap: false 43 | }, 44 | 45 | build: { 46 | // Template for index.html 47 | index: path.resolve(__dirname, '../../main/resources/static/dist/index.html'), 48 | 49 | // Paths 50 | assetsRoot: path.resolve(__dirname, '../../main/resources/static'), 51 | assetsSubDirectory: 'dist/static', 52 | 53 | /** 54 | * You can set by youself according to actual condition 55 | * You will need to set this if you plan to deploy your site under a sub path, 56 | * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/, 57 | * then assetsPublicPath should be set to "/bar/". 58 | * In most cases please use '/' !!! 59 | */ 60 | assetsPublicPath: '/', 61 | 62 | /** 63 | * Source Maps 64 | */ 65 | 66 | productionSourceMap: false, 67 | // https://webpack.js.org/configuration/devtool/#production 68 | devtool: 'source-map', 69 | 70 | // Gzip off by default as many popular static hosts such as 71 | // Surge or Netlify already gzip all static assets for you. 72 | // Before setting to `true`, make sure to: 73 | // npm install --save-dev compression-webpack-plugin 74 | productionGzip: false, 75 | productionGzipExtensions: ['js', 'css'], 76 | 77 | // Run the build command with an extra argument to 78 | // View the bundle analyzer report after build finishes: 79 | // `npm run build --report` 80 | // Set to `true` or `false` to always turn it on or off 81 | bundleAnalyzerReport: process.env.npm_config_report || false, 82 | 83 | // `npm run build:prod --generate_report` 84 | generateAnalyzerReport: process.env.npm_config_generate_report || false 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/vue-admin-template/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | BASE_API: '"http://localhost:8080/api"', 5 | } 6 | -------------------------------------------------------------------------------- /src/vue-admin-template/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fwing1987/SpringBoot-Shiro-Vue/554b8392185c7ecb9ca56ba42da0ae0a30d32738/src/vue-admin-template/favicon.ico -------------------------------------------------------------------------------- /src/vue-admin-template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-admin-template 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/vue-admin-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-template", 3 | "version": "3.8.0", 4 | "license": "MIT", 5 | "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", 6 | "author": "Pan ", 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js", 11 | "build:report": "npm_config_report=true npm run build", 12 | "lint": "eslint --ext .js,.vue src", 13 | "test": "npm run lint", 14 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" 15 | }, 16 | "dependencies": { 17 | "axios": "0.18.0", 18 | "element-ui": "2.4.6", 19 | "js-cookie": "2.2.0", 20 | "normalize.css": "7.0.0", 21 | "nprogress": "0.2.0", 22 | "vue": "2.5.17", 23 | "vue-router": "3.0.1", 24 | "vuex": "3.0.1" 25 | }, 26 | "devDependencies": { 27 | "autoprefixer": "8.5.0", 28 | "babel-core": "6.26.0", 29 | "babel-eslint": "8.2.6", 30 | "babel-helper-vue-jsx-merge-props": "2.0.3", 31 | "babel-loader": "7.1.5", 32 | "babel-plugin-syntax-jsx": "6.18.0", 33 | "babel-plugin-transform-runtime": "6.23.0", 34 | "babel-plugin-transform-vue-jsx": "3.7.0", 35 | "babel-preset-env": "1.7.0", 36 | "babel-preset-stage-2": "6.24.1", 37 | "chalk": "2.4.1", 38 | "copy-webpack-plugin": "4.5.2", 39 | "css-loader": "1.0.0", 40 | "eslint": "4.19.1", 41 | "eslint-friendly-formatter": "4.0.1", 42 | "eslint-loader": "2.0.0", 43 | "eslint-plugin-vue": "4.7.1", 44 | "eventsource-polyfill": "0.9.6", 45 | "file-loader": "1.1.11", 46 | "friendly-errors-webpack-plugin": "1.7.0", 47 | "html-webpack-plugin": "4.0.0-alpha", 48 | "mini-css-extract-plugin": "0.4.1", 49 | "node-notifier": "5.2.1", 50 | "node-sass": "^4.7.2", 51 | "optimize-css-assets-webpack-plugin": "5.0.0", 52 | "ora": "3.0.0", 53 | "path-to-regexp": "2.4.0", 54 | "portfinder": "1.0.16", 55 | "postcss-import": "12.0.0", 56 | "postcss-loader": "2.1.6", 57 | "postcss-url": "7.3.2", 58 | "rimraf": "2.6.2", 59 | "sass-loader": "7.0.3", 60 | "script-ext-html-webpack-plugin": "2.0.1", 61 | "semver": "5.5.0", 62 | "shelljs": "0.8.2", 63 | "svg-sprite-loader": "3.8.0", 64 | "svgo": "1.0.5", 65 | "uglifyjs-webpack-plugin": "1.2.7", 66 | "url-loader": "1.0.1", 67 | "vue-loader": "15.3.0", 68 | "vue-style-loader": "4.1.2", 69 | "vue-template-compiler": "2.5.17", 70 | "webpack": "4.16.5", 71 | "webpack-bundle-analyzer": "2.13.1", 72 | "webpack-cli": "3.1.0", 73 | "webpack-dev-server": "3.1.5", 74 | "webpack-merge": "4.1.4" 75 | }, 76 | "engines": { 77 | "node": ">= 6.0.0", 78 | "npm": ">= 3.0.0" 79 | }, 80 | "browserslist": [ 81 | "> 1%", 82 | "last 2 versions", 83 | "not ie <= 8" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login(username, password) { 4 | return request({ 5 | method: 'post', 6 | data: { 7 | cmd: 'signin', 8 | username, 9 | password 10 | } 11 | }) 12 | } 13 | 14 | export function getInfo() { 15 | return request({ 16 | method: 'post', 17 | data: { 18 | cmd: 'getUserInfo' 19 | } 20 | }) 21 | } 22 | 23 | export function logout() { 24 | return request({ 25 | method: 'post', 26 | data: { 27 | cmd: 'logout' 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getList(params) { 4 | return request({ 5 | url: 'https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin/table/list', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fwing1987/SpringBoot-Shiro-Vue/554b8392185c7ecb9ca56ba42da0ae0a30d32738/src/vue-admin-template/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/vue-admin-template/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fwing1987/SpringBoot-Shiro-Vue/554b8392185c7ecb9ca56ba42da0ae0a30d32738/src/vue-admin-template/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/vue-admin-template/src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ item.meta.title }} 6 | {{ item.meta.title }} 7 | 8 | 9 | 10 | 11 | 12 | 49 | 50 | 62 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 19 | 22 | 25 | 26 | 27 | 28 | 29 | 44 | 45 | 59 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 34 | 35 | 44 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon' // svg组件 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const requireAll = requireContext => requireContext.keys().map(requireContext) 8 | const req = require.context('./svg', false, /\.svg$/) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | import locale from 'element-ui/lib/locale/lang/en' // lang i18n 8 | 9 | import '@/styles/index.scss' // global css 10 | 11 | import App from './App' 12 | import router from './router' 13 | import store from './store' 14 | 15 | import '@/icons' // icon 16 | import '@/permission' // permission control 17 | 18 | Vue.use(ElementUI, { locale }) 19 | 20 | Vue.config.productionTip = false 21 | 22 | new Vue({ 23 | el: '#app', 24 | router, 25 | store, 26 | render: h => h(App) 27 | }) 28 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import NProgress from 'nprogress' // Progress 进度条 4 | import 'nprogress/nprogress.css'// Progress 进度条样式 5 | import { Message } from 'element-ui' 6 | import { getToken } from '@/utils/auth' // 验权 7 | 8 | const whiteList = ['/login'] // 不重定向白名单 9 | router.beforeEach((to, from, next) => { 10 | NProgress.start() 11 | if (getToken()) { 12 | if (to.path === '/login') { 13 | next({ path: '/' }) 14 | NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it 15 | } else { 16 | if (store.getters.roles.length === 0) { 17 | store.dispatch('GetInfo').then(res => { // 拉取用户信息 18 | next(to) 19 | }).catch((err) => { 20 | store.dispatch('FedLogOut').then(() => { 21 | Message.error(err || 'Verification failed, please login again') 22 | next({ path: '/' }) 23 | }) 24 | }) 25 | } else { 26 | next() 27 | } 28 | } 29 | } else { 30 | if (whiteList.indexOf(to.path) !== -1) { 31 | next() 32 | } else { 33 | next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 34 | NProgress.done() 35 | } 36 | } 37 | }) 38 | 39 | router.afterEach(() => { 40 | NProgress.done() // 结束Progress 41 | }) 42 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | // in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading; 5 | // detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading 6 | 7 | Vue.use(Router) 8 | 9 | /* Layout */ 10 | import Layout from '../views/layout/Layout' 11 | 12 | /** 13 | * hidden: true if `hidden:true` will not show in the sidebar(default is false) 14 | * alwaysShow: true if set true, will always show the root menu, whatever its child routes length 15 | * if not set alwaysShow, only more than one route under the children 16 | * it will becomes nested mode, otherwise not show the root menu 17 | * redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb 18 | * name:'router-name' the name is used by (must set!!!) 19 | * meta : { 20 | title: 'title' the name show in submenu and breadcrumb (recommend set) 21 | icon: 'svg-name' the icon show in the sidebar, 22 | } 23 | **/ 24 | export const constantRouterMap = [ 25 | { path: '/login', component: () => import('@/views/login/index'), hidden: true }, 26 | { path: '/404', component: () => import('@/views/404'), hidden: true }, 27 | 28 | { 29 | path: '/', 30 | component: Layout, 31 | redirect: '/dashboard', 32 | name: 'Dashboard', 33 | hidden: true, 34 | children: [{ 35 | path: 'dashboard', 36 | component: () => import('@/views/dashboard/index') 37 | }] 38 | } 39 | ] 40 | 41 | export const asyncRouterMap = [ 42 | { 43 | path: '/example', 44 | component: Layout, 45 | redirect: '/example/table', 46 | name: 'Example', 47 | meta: { title: 'Example', icon: 'example' }, 48 | alwaysShow: true, 49 | children: [ 50 | { 51 | path: 'table', 52 | name: 'Table', 53 | component: () => import('@/views/table/index'), 54 | meta: { title: 'Table', icon: 'table' }, 55 | menu: '/example/table' 56 | }, 57 | { 58 | path: 'tree', 59 | name: 'Tree', 60 | component: () => import('@/views/tree/index'), 61 | meta: { title: 'Tree', icon: 'tree' }, 62 | menu: '/example/tree' 63 | } 64 | ] 65 | }, 66 | 67 | { 68 | path: '/form', 69 | component: Layout, 70 | children: [ 71 | { 72 | path: 'index', 73 | name: 'Form', 74 | component: () => import('@/views/form/index'), 75 | meta: { title: 'Form', icon: 'form' }, 76 | menu: '/form' 77 | } 78 | ] 79 | }, 80 | 81 | { 82 | path: '/nested', 83 | component: Layout, 84 | redirect: '/nested/menu1', 85 | name: 'Nested', 86 | meta: { 87 | title: 'Nested', 88 | icon: 'nested' 89 | }, 90 | children: [ 91 | { 92 | path: 'menu1', 93 | component: () => import('@/views/nested/menu1/index'), // Parent router-view 94 | name: 'Menu1', 95 | meta: { title: 'Menu1' }, 96 | menu: '/nested/menu1', 97 | children: [ 98 | { 99 | path: 'menu1-1', 100 | component: () => import('@/views/nested/menu1/menu1-1'), 101 | name: 'Menu1-1', 102 | meta: { title: 'Menu1-1' }, 103 | menu: '/nested/menu1/menu1-1' 104 | }, 105 | { 106 | path: 'menu1-2', 107 | component: () => import('@/views/nested/menu1/menu1-2'), 108 | name: 'Menu1-2', 109 | meta: { title: 'Menu1-2' }, 110 | menu: '/nested/menu1/menu1-2', 111 | children: [ 112 | { 113 | path: 'menu1-2-1', 114 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'), 115 | name: 'Menu1-2-1', 116 | meta: { title: 'Menu1-2-1' }, 117 | menu: '/nested/menu1/menu1-2/menu1-2-1' 118 | }, 119 | { 120 | path: 'menu1-2-2', 121 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'), 122 | name: 'Menu1-2-2', 123 | meta: { title: 'Menu1-2-2' }, 124 | menu: '/nested/menu1/menu1-2/menu1-2-2' 125 | } 126 | ] 127 | }, 128 | { 129 | path: 'menu1-3', 130 | component: () => import('@/views/nested/menu1/menu1-3'), 131 | name: 'Menu1-3', 132 | meta: { title: 'Menu1-3' }, 133 | menu: '/nested/menu1/menu1-3' 134 | } 135 | ] 136 | }, 137 | { 138 | path: 'menu2', 139 | component: () => import('@/views/nested/menu2/index'), 140 | meta: { title: 'menu2' }, 141 | menu: '/nested/menu2' 142 | } 143 | ] 144 | }, 145 | 146 | { 147 | path: 'external-link', 148 | component: Layout, 149 | children: [ 150 | { 151 | path: 'https://panjiachen.github.io/vue-element-admin-site/#/', 152 | meta: { title: 'External Link', icon: 'link' }, 153 | menu: 'external-link' 154 | } 155 | ] 156 | }, 157 | // 一定注意需要在最后添加,否则动态添加的路由都会先路由到这个通配路由上,详细的问题见https://github.com/vuejs/vue-router/issues/1176 158 | { path: '*', redirect: '/404', hidden: true } 159 | ] 160 | 161 | export default new Router({ 162 | // mode: 'history', //后端支持可开 163 | scrollBehavior: () => ({ y: 0 }), 164 | routes: constantRouterMap 165 | }) 166 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name, 7 | roles: state => state.user.roles, 8 | routes: state => state.user.routes 9 | } 10 | export default getters 11 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import user from './modules/user' 5 | import getters from './getters' 6 | 7 | Vue.use(Vuex) 8 | 9 | const store = new Vuex.Store({ 10 | modules: { 11 | app, 12 | user 13 | }, 14 | getters 15 | }) 16 | 17 | export default store 18 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const app = { 4 | state: { 5 | sidebar: { 6 | opened: !+Cookies.get('sidebarStatus'), 7 | withoutAnimation: false 8 | }, 9 | device: 'desktop' 10 | }, 11 | mutations: { 12 | TOGGLE_SIDEBAR: state => { 13 | if (state.sidebar.opened) { 14 | Cookies.set('sidebarStatus', 1) 15 | } else { 16 | Cookies.set('sidebarStatus', 0) 17 | } 18 | state.sidebar.opened = !state.sidebar.opened 19 | state.sidebar.withoutAnimation = false 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 1) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | }, 30 | actions: { 31 | ToggleSideBar: ({ commit }) => { 32 | commit('TOGGLE_SIDEBAR') 33 | }, 34 | CloseSideBar({ commit }, { withoutAnimation }) { 35 | commit('CLOSE_SIDEBAR', withoutAnimation) 36 | }, 37 | ToggleDevice({ commit }, device) { 38 | commit('TOGGLE_DEVICE', device) 39 | } 40 | } 41 | } 42 | 43 | export default app 44 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { login, logout, getInfo } from '@/api/login' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | import router, { asyncRouterMap } from '@/router' 4 | 5 | function filterMenus(routes, serverMenus) { 6 | var validRoutes = routes.filter(router => { 7 | // debugger 8 | for (var index in serverMenus) { 9 | var serverMenu = serverMenus[index] 10 | if (router.menu === serverMenu.menu_path) { 11 | return true 12 | } 13 | if (router.children && router.children.length > 0) { 14 | router.children = filterMenus(router.children, serverMenus) 15 | if (router.children && router.children.length > 0) { 16 | return true 17 | } 18 | } 19 | } 20 | return false 21 | }) 22 | return validRoutes 23 | } 24 | const user = { 25 | state: { 26 | token: getToken(), 27 | name: '', 28 | avatar: '', 29 | roles: [], 30 | routes: [] 31 | }, 32 | 33 | mutations: { 34 | SET_TOKEN: (state, token) => { 35 | state.token = token 36 | }, 37 | SET_NAME: (state, name) => { 38 | state.name = name 39 | }, 40 | SET_AVATAR: (state, avatar) => { 41 | state.avatar = avatar 42 | }, 43 | SET_ROLES: (state, roles) => { 44 | state.roles = roles 45 | }, 46 | SET_ROUTES: (state, menus) => { 47 | state.routes = filterMenus(asyncRouterMap, menus) 48 | router.addRoutes(state.routes) 49 | } 50 | }, 51 | 52 | actions: { 53 | // 登录 54 | Login({ commit }, userInfo) { 55 | const username = userInfo.username.trim() 56 | return new Promise((resolve, reject) => { 57 | login(username, userInfo.password).then(response => { 58 | const data = response.body 59 | setToken(data) 60 | commit('SET_TOKEN', data) 61 | resolve() 62 | }).catch(error => { 63 | reject(error) 64 | }) 65 | }) 66 | }, 67 | 68 | // 获取用户信息 69 | GetInfo({ commit, state }) { 70 | return new Promise((resolve, reject) => { 71 | getInfo(state.token).then(response => { 72 | const data = response.body 73 | if (data.roleList && data.roleList.length > 0) { // 验证返回的roles是否是一个非空数组 74 | commit('SET_ROLES', data.roleList) 75 | commit('SET_ROUTES', data.menuList) 76 | } else { 77 | reject('getInfo: roles must be a non-null array !') 78 | } 79 | commit('SET_NAME', data.nickname) 80 | commit('SET_AVATAR', data.avatar) 81 | resolve(response) 82 | }).catch(error => { 83 | reject(error) 84 | }) 85 | }) 86 | }, 87 | 88 | // 登出 89 | LogOut({ commit, state }) { 90 | return new Promise((resolve, reject) => { 91 | logout(state.token).then(() => { 92 | commit('SET_TOKEN', '') 93 | commit('SET_ROLES', []) 94 | removeToken() 95 | resolve() 96 | }).catch(error => { 97 | reject(error) 98 | }) 99 | }) 100 | }, 101 | 102 | // 前端 登出 103 | FedLogOut({ commit }) { 104 | return new Promise(resolve => { 105 | commit('SET_TOKEN', '') 106 | removeToken() 107 | resolve() 108 | }) 109 | } 110 | } 111 | } 112 | 113 | export default user 114 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | //to reset element-ui default css 2 | .el-upload { 3 | input[type="file"] { 4 | display: none !important; 5 | } 6 | } 7 | 8 | .el-upload__input { 9 | display: none; 10 | } 11 | 12 | //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461 13 | .el-dialog { 14 | transform: none; 15 | left: 0; 16 | position: relative; 17 | margin: 0 auto; 18 | } 19 | 20 | //element ui upload 21 | .upload-container { 22 | .el-upload { 23 | width: 100%; 24 | .el-upload-dragger { 25 | width: 100%; 26 | height: 200px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | 7 | body { 8 | height: 100%; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | label { 16 | font-weight: 700; 17 | } 18 | 19 | html { 20 | height: 100%; 21 | box-sizing: border-box; 22 | } 23 | 24 | #app{ 25 | height: 100%; 26 | } 27 | 28 | *, 29 | *:before, 30 | *:after { 31 | box-sizing: inherit; 32 | } 33 | 34 | a, 35 | a:focus, 36 | a:hover { 37 | cursor: pointer; 38 | color: inherit; 39 | outline: none; 40 | text-decoration: none; 41 | } 42 | 43 | div:focus{ 44 | outline: none; 45 | } 46 | 47 | a:focus, 48 | a:active { 49 | outline: none; 50 | } 51 | 52 | a, 53 | a:focus, 54 | a:hover { 55 | cursor: pointer; 56 | color: inherit; 57 | text-decoration: none; 58 | } 59 | 60 | .clearfix { 61 | &:after { 62 | visibility: hidden; 63 | display: block; 64 | font-size: 0; 65 | content: " "; 66 | clear: both; 67 | height: 0; 68 | } 69 | } 70 | 71 | //main-container全局样式 72 | .app-main{ 73 | min-height: 100% 74 | } 75 | 76 | .app-container { 77 | padding: 20px; 78 | } 79 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | &::-webkit-scrollbar { 14 | width: 6px; 15 | } 16 | &::-webkit-scrollbar-thumb { 17 | background: #99a9bf; 18 | border-radius: 20px; 19 | } 20 | } 21 | 22 | @mixin relative { 23 | position: relative; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | // 主体区域 3 | .main-container { 4 | min-height: 100%; 5 | transition: margin-left .28s; 6 | margin-left: 180px; 7 | position: relative; 8 | } 9 | // 侧边栏 10 | .sidebar-container { 11 | transition: width 0.28s; 12 | width: 180px !important; 13 | height: 100%; 14 | position: fixed; 15 | font-size: 0px; 16 | top: 0; 17 | bottom: 0; 18 | left: 0; 19 | z-index: 1001; 20 | overflow: hidden; 21 | //reset element-ui css 22 | .horizontal-collapse-transition { 23 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 24 | } 25 | .el-scrollbar__bar.is-vertical{ 26 | right: 0px; 27 | } 28 | .scrollbar-wrapper { 29 | overflow-x: hidden!important; 30 | .el-scrollbar__view { 31 | height: 100%; 32 | } 33 | } 34 | .is-horizontal { 35 | display: none; 36 | } 37 | a { 38 | display: inline-block; 39 | width: 100%; 40 | overflow: hidden; 41 | } 42 | .svg-icon { 43 | margin-right: 16px; 44 | } 45 | .el-menu { 46 | border: none; 47 | height: 100%; 48 | width: 100% !important; 49 | } 50 | .is-active > .el-submenu__title{ 51 | color: #f4f4f5!important; 52 | } 53 | } 54 | .hideSidebar { 55 | .sidebar-container { 56 | width: 36px !important; 57 | } 58 | .main-container { 59 | margin-left: 36px; 60 | } 61 | .submenu-title-noDropdown { 62 | padding-left: 10px !important; 63 | position: relative; 64 | .el-tooltip { 65 | padding: 0 10px !important; 66 | } 67 | } 68 | .el-submenu { 69 | overflow: hidden; 70 | &>.el-submenu__title { 71 | padding-left: 10px !important; 72 | .el-submenu__icon-arrow { 73 | display: none; 74 | } 75 | } 76 | } 77 | .el-menu--collapse { 78 | .el-submenu { 79 | &>.el-submenu__title { 80 | &>span { 81 | height: 0; 82 | width: 0; 83 | overflow: hidden; 84 | visibility: hidden; 85 | display: inline-block; 86 | } 87 | } 88 | } 89 | } 90 | } 91 | .sidebar-container .nest-menu .el-submenu>.el-submenu__title, 92 | .sidebar-container .el-submenu .el-menu-item { 93 | min-width: 180px !important; 94 | background-color: $subMenuBg !important; 95 | &:hover { 96 | background-color: $menuHover !important; 97 | } 98 | } 99 | .el-menu--collapse .el-menu .el-submenu { 100 | min-width: 180px !important; 101 | } 102 | 103 | //适配移动端 104 | .mobile { 105 | .main-container { 106 | margin-left: 0px; 107 | } 108 | .sidebar-container { 109 | transition: transform .28s; 110 | width: 180px !important; 111 | } 112 | &.hideSidebar { 113 | .sidebar-container { 114 | transition-duration: 0.3s; 115 | transform: translate3d(-180px, 0, 0); 116 | } 117 | } 118 | } 119 | .withoutAnimation { 120 | .main-container, 121 | .sidebar-container { 122 | transition: none; 123 | } 124 | } 125 | } 126 | 127 | .el-menu--vertical{ 128 | & >.el-menu{ 129 | .svg-icon{ 130 | margin-right: 16px; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | //globl transition css 2 | 3 | /*fade*/ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /*fade-transform*/ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | .fade-transform-enter { 20 | opacity: 0; 21 | transform: translateX(-30px); 22 | } 23 | .fade-transform-leave-to { 24 | opacity: 0; 25 | transform: translateX(30px); 26 | } 27 | 28 | /*fade*/ 29 | .breadcrumb-enter-active, 30 | .breadcrumb-leave-active { 31 | transition: all .5s; 32 | } 33 | 34 | .breadcrumb-enter, 35 | .breadcrumb-leave-active { 36 | opacity: 0; 37 | transform: translateX(20px); 38 | } 39 | 40 | .breadcrumb-move { 41 | transition: all .5s; 42 | } 43 | 44 | .breadcrumb-leave-active { 45 | position: absolute; 46 | } 47 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | //sidebar 2 | $menuBg:#304156; 3 | $subMenuBg:#1f2d3d; 4 | $menuHover:#001528; 5 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Admin-Token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 16/11/18. 3 | */ 4 | 5 | export function parseTime(time, cFormat) { 6 | if (arguments.length === 0) { 7 | return null 8 | } 9 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 10 | let date 11 | if (typeof time === 'object') { 12 | date = time 13 | } else { 14 | if (('' + time).length === 10) time = parseInt(time) * 1000 15 | date = new Date(time) 16 | } 17 | const formatObj = { 18 | y: date.getFullYear(), 19 | m: date.getMonth() + 1, 20 | d: date.getDate(), 21 | h: date.getHours(), 22 | i: date.getMinutes(), 23 | s: date.getSeconds(), 24 | a: date.getDay() 25 | } 26 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 27 | let value = formatObj[key] 28 | // Note: getDay() returns 0 on Sunday 29 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } 30 | if (result.length > 0 && value < 10) { 31 | value = '0' + value 32 | } 33 | return value || 0 34 | }) 35 | return time_str 36 | } 37 | 38 | export function formatTime(time, option) { 39 | time = +time * 1000 40 | const d = new Date(time) 41 | const now = Date.now() 42 | 43 | const diff = (now - d) / 1000 44 | 45 | if (diff < 30) { 46 | return '刚刚' 47 | } else if (diff < 3600) { 48 | // less 1 hour 49 | return Math.ceil(diff / 60) + '分钟前' 50 | } else if (diff < 3600 * 24) { 51 | return Math.ceil(diff / 3600) + '小时前' 52 | } else if (diff < 3600 * 24 * 2) { 53 | return '1天前' 54 | } 55 | if (option) { 56 | return parseTime(time, option) 57 | } else { 58 | return ( 59 | d.getMonth() + 60 | 1 + 61 | '月' + 62 | d.getDate() + 63 | '日' + 64 | d.getHours() + 65 | '时' + 66 | d.getMinutes() + 67 | '分' 68 | ) 69 | } 70 | } 71 | 72 | export function isExternal(path) { 73 | return /^(https?:|mailto:|tel:)/.test(path) 74 | } 75 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import store from '../store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // 创建axios实例 7 | const service = axios.create({ 8 | baseURL: process.env.BASE_API, // api 的 base_url 9 | timeout: 5000 // 请求超时时间 10 | }) 11 | 12 | // request拦截器 13 | service.interceptors.request.use( 14 | config => { 15 | if (store.getters.token) { 16 | config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 17 | } 18 | return config 19 | }, 20 | error => { 21 | // Do something with request error 22 | console.log(error) // for debug 23 | Promise.reject(error) 24 | } 25 | ) 26 | 27 | // response 拦截器 28 | service.interceptors.response.use( 29 | response => { 30 | /** 31 | * code为非20000是抛错 可结合自己业务进行修改 32 | */ 33 | const res = response.data 34 | if (res.code !== 20000) { 35 | Message({ 36 | message: res.message, 37 | type: 'error', 38 | duration: 5 * 1000 39 | }) 40 | 41 | // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; 42 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 43 | MessageBox.confirm( 44 | '你已被登出,可以取消继续留在该页面,或者重新登录', 45 | '确定登出', 46 | { 47 | confirmButtonText: '重新登录', 48 | cancelButtonText: '取消', 49 | type: 'warning' 50 | } 51 | ).then(() => { 52 | store.dispatch('FedLogOut').then(() => { 53 | location.reload() // 为了重新实例化vue-router对象 避免bug 54 | }) 55 | }) 56 | } 57 | return Promise.reject('error') 58 | } else { 59 | return response.data 60 | } 61 | }, 62 | error => { 63 | console.log('err' + error) // for debug 64 | Message({ 65 | message: error.message, 66 | type: 'error', 67 | duration: 5 * 1000 68 | }) 69 | return Promise.reject(error) 70 | } 71 | ) 72 | 73 | export default service 74 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 16/11/18. 3 | */ 4 | 5 | export function isvalidUsername(str) { 6 | const valid_map = ['admin', 'editor'] 7 | return valid_map.indexOf(str.trim()) >= 0 8 | } 9 | 10 | /* 合法uri*/ 11 | export function validateURL(textval) { 12 | const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 13 | return urlregex.test(textval) 14 | } 15 | 16 | /* 小写字母*/ 17 | export function validateLowerCase(str) { 18 | const reg = /^[a-z]+$/ 19 | return reg.test(str) 20 | } 21 | 22 | /* 大写字母*/ 23 | export function validateUpperCase(str) { 24 | const reg = /^[A-Z]+$/ 25 | return reg.test(str) 26 | } 27 | 28 | /* 大小写字母*/ 29 | export function validatAlphabets(str) { 30 | const reg = /^[A-Za-z]+$/ 31 | return reg.test(str) 32 | } 33 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | OOPS! 12 | 版权所有 13 | 华尔街见闻 14 | 15 | {{ message }} 16 | 请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告 17 | 返回首页 18 | 19 | 20 | 21 | 22 | 23 | 34 | 35 | 229 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | name:{{ name }} 4 | roles:{{ role }} 5 | 6 | 7 | 8 | 21 | 22 | 33 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/form/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | - 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Create 44 | Cancel 45 | 46 | 47 | 48 | 49 | 50 | 79 | 80 | 85 | 86 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 47 | 48 | 70 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 | 22 | 30 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Home 14 | 15 | 16 | 17 | LogOut 18 | 19 | 20 | 21 | 22 | 23 | 24 | 52 | 53 | 94 | 95 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 40 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 102 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 1024 5 | const RATIO = 3 6 | 7 | export default { 8 | watch: { 9 | $route(route) { 10 | if (this.device === 'mobile' && this.sidebar.opened) { 11 | store.dispatch('CloseSideBar', { withoutAnimation: false }) 12 | } 13 | } 14 | }, 15 | beforeMount() { 16 | window.addEventListener('resize', this.resizeHandler) 17 | }, 18 | mounted() { 19 | const isMobile = this.isMobile() 20 | if (isMobile) { 21 | store.dispatch('ToggleDevice', 'mobile') 22 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 23 | } 24 | }, 25 | methods: { 26 | isMobile() { 27 | const rect = body.getBoundingClientRect() 28 | return rect.width - RATIO < WIDTH 29 | }, 30 | resizeHandler() { 31 | if (!document.hidden) { 32 | const isMobile = this.isMobile() 33 | store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop') 34 | 35 | if (isMobile) { 36 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | vue-admin-template 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Sign in 29 | 30 | 31 | 32 | username: admin 33 | password: admin 34 | 35 | 36 | 37 | 38 | 39 | 110 | 111 | 144 | 145 | 199 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/nested/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/nested/menu1/menu1-1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/nested/menu1/menu1-3/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/table/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | {{ scope.$index }} 13 | 14 | 15 | 16 | 17 | {{ scope.row.title }} 18 | 19 | 20 | 21 | 22 | {{ scope.row.author }} 23 | 24 | 25 | 26 | 27 | {{ scope.row.pageviews }} 28 | 29 | 30 | 31 | 32 | {{ scope.row.status }} 33 | 34 | 35 | 36 | 37 | 38 | {{ scope.row.display_time }} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 81 | -------------------------------------------------------------------------------- /src/vue-admin-template/src/views/tree/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 78 | 79 | -------------------------------------------------------------------------------- /src/vue-admin-template/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fwing1987/SpringBoot-Shiro-Vue/554b8392185c7ecb9ca56ba42da0ae0a30d32738/src/vue-admin-template/static/.gitkeep -------------------------------------------------------------------------------- /test.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 50710 7 | Source Host : localhost 8 | Source Database : test 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50710 12 | File Encoding : utf-8 13 | 14 | Date: 11/06/2018 10:49:33 AM 15 | */ 16 | 17 | SET NAMES utf8; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for `sys_menu` 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `sys_menu`; 24 | CREATE TABLE `sys_menu` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT, 26 | `menu_name` varchar(45) NOT NULL, 27 | `menu_path` varchar(45) NOT NULL, 28 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 29 | `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 30 | PRIMARY KEY (`id`) 31 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 32 | 33 | -- ---------------------------- 34 | -- Records of `sys_menu` 35 | -- ---------------------------- 36 | BEGIN; 37 | INSERT INTO `sys_menu` VALUES ('1', '例-table', '/example/table', '2018-10-26 19:38:30', '2018-10-30 11:42:15'), ('2', '表单', '/form', '2018-10-30 14:43:19', '2018-10-30 15:35:35'); 38 | COMMIT; 39 | 40 | -- ---------------------------- 41 | -- Table structure for `sys_permission` 42 | -- ---------------------------- 43 | DROP TABLE IF EXISTS `sys_permission`; 44 | CREATE TABLE `sys_permission` ( 45 | `id` int(11) NOT NULL AUTO_INCREMENT, 46 | `permission_code` varchar(45) DEFAULT NULL, 47 | `permission_desc` varchar(128) DEFAULT NULL, 48 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 49 | PRIMARY KEY (`id`) 50 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 51 | 52 | -- ---------------------------- 53 | -- Table structure for `sys_role` 54 | -- ---------------------------- 55 | DROP TABLE IF EXISTS `sys_role`; 56 | CREATE TABLE `sys_role` ( 57 | `id` int(11) NOT NULL AUTO_INCREMENT, 58 | `rolename` varchar(45) NOT NULL, 59 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 60 | PRIMARY KEY (`id`) 61 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 62 | 63 | -- ---------------------------- 64 | -- Records of `sys_role` 65 | -- ---------------------------- 66 | BEGIN; 67 | INSERT INTO `sys_role` VALUES ('1', '管理员', '2018-10-25 16:33:34'); 68 | COMMIT; 69 | 70 | -- ---------------------------- 71 | -- Table structure for `sys_role_menu` 72 | -- ---------------------------- 73 | DROP TABLE IF EXISTS `sys_role_menu`; 74 | CREATE TABLE `sys_role_menu` ( 75 | `id` int(11) NOT NULL AUTO_INCREMENT, 76 | `role_id` int(11) NOT NULL, 77 | `menu_id` int(11) NOT NULL, 78 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 79 | PRIMARY KEY (`id`) 80 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 81 | 82 | -- ---------------------------- 83 | -- Records of `sys_role_menu` 84 | -- ---------------------------- 85 | BEGIN; 86 | INSERT INTO `sys_role_menu` VALUES ('1', '1', '1', '2018-10-27 20:16:50'), ('2', '1', '2', '2018-10-30 14:44:48'); 87 | COMMIT; 88 | 89 | -- ---------------------------- 90 | -- Table structure for `sys_role_permission` 91 | -- ---------------------------- 92 | DROP TABLE IF EXISTS `sys_role_permission`; 93 | CREATE TABLE `sys_role_permission` ( 94 | `id` int(11) NOT NULL AUTO_INCREMENT, 95 | `role_id` int(11) NOT NULL, 96 | `permission_id` int(11) NOT NULL, 97 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 98 | PRIMARY KEY (`id`) 99 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 100 | 101 | -- ---------------------------- 102 | -- Table structure for `sys_user` 103 | -- ---------------------------- 104 | DROP TABLE IF EXISTS `sys_user`; 105 | CREATE TABLE `sys_user` ( 106 | `id` int(11) NOT NULL AUTO_INCREMENT, 107 | `username` varchar(45) NOT NULL, 108 | `password` varchar(45) NOT NULL, 109 | `salt` varchar(10) DEFAULT NULL, 110 | `nickname` varchar(45) NOT NULL, 111 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 112 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 113 | `state` int(2) NOT NULL DEFAULT '1' COMMENT '1:有效\n2:冻结\n', 114 | `avatar` varchar(128) DEFAULT NULL, 115 | PRIMARY KEY (`id`) 116 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 117 | 118 | -- ---------------------------- 119 | -- Records of `sys_user` 120 | -- ---------------------------- 121 | BEGIN; 122 | INSERT INTO `sys_user` VALUES ('1', 'admin', 'f10e422feea3e0d1e60d95c419dd1bef', '1faf', '王大锤', '2018-10-25 16:33:11', '2018-10-31 14:32:24', '1', 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif'); 123 | COMMIT; 124 | 125 | -- ---------------------------- 126 | -- Table structure for `sys_user_role` 127 | -- ---------------------------- 128 | DROP TABLE IF EXISTS `sys_user_role`; 129 | CREATE TABLE `sys_user_role` ( 130 | `id` int(11) NOT NULL AUTO_INCREMENT, 131 | `user_id` int(11) NOT NULL, 132 | `role_id` int(11) NOT NULL, 133 | `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 134 | PRIMARY KEY (`id`) 135 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 136 | 137 | -- ---------------------------- 138 | -- Records of `sys_user_role` 139 | -- ---------------------------- 140 | BEGIN; 141 | INSERT INTO `sys_user_role` VALUES ('1', '1', '1', '2018-10-25 16:33:47'); 142 | COMMIT; 143 | 144 | SET FOREIGN_KEY_CHECKS = 1; 145 | --------------------------------------------------------------------------------