├── admin ├── static │ └── .gitkeep ├── .eslintignore ├── test │ ├── unit │ │ ├── setup.js │ │ ├── .eslintrc │ │ ├── specs │ │ │ └── HelloWorld.spec.js │ │ └── jest.conf.js │ └── e2e │ │ ├── specs │ │ └── test.js │ │ ├── custom-assertions │ │ └── elementCount.js │ │ ├── nightwatch.conf.js │ │ └── runner.js ├── favicon.ico ├── src │ ├── router │ │ ├── _import_production.js │ │ ├── _import_development.js │ │ └── index.js │ ├── assets │ │ ├── 401Images │ │ │ └── 401.gif │ │ └── 404Images │ │ │ ├── 404.png │ │ │ └── 404cloud.png │ ├── utils │ │ ├── hasPermission.js │ │ ├── token.js │ │ ├── validate.js │ │ ├── request.js │ │ └── index.js │ ├── styles │ │ ├── element-variables.scss │ │ ├── mixin.scss │ │ └── index.scss │ ├── views │ │ ├── layout │ │ │ ├── components │ │ │ │ ├── index.js │ │ │ │ ├── AppMain.vue │ │ │ │ ├── Sidebar │ │ │ │ │ ├── index.vue │ │ │ │ │ └── SidebarItem.vue │ │ │ │ ├── Levelbar.vue │ │ │ │ └── Navbar.vue │ │ │ └── Layout.vue │ │ ├── dashboard │ │ │ └── index.vue │ │ └── errorPage │ │ │ └── 401.vue │ ├── icons │ │ ├── index.js │ │ └── svg │ │ │ ├── name.svg │ │ │ ├── permission.svg │ │ │ ├── report.svg │ │ │ ├── log.svg │ │ │ ├── password.svg │ │ │ ├── eye.svg │ │ │ ├── wechat.svg │ │ │ ├── dashboard.svg │ │ │ └── role.svg │ ├── store │ │ ├── index.js │ │ ├── getters.js │ │ └── modules │ │ │ ├── app.js │ │ │ ├── permission.js │ │ │ └── account.js │ ├── components │ │ ├── Icon-svg │ │ │ └── index.vue │ │ └── Hamburger │ │ │ └── index.vue │ ├── App.vue │ ├── main.js │ ├── api │ │ ├── role.js │ │ └── account.js │ └── permission.js ├── config │ ├── prod.env.js │ ├── test.env.js │ ├── dev.env.js │ └── index.js ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── index.html ├── .babelrc ├── README.md ├── build │ ├── vue-loader.conf.js │ ├── build.js │ ├── check-versions.js │ ├── utils.js │ ├── webpack.base.conf.js │ └── webpack.dev.conf.js └── package.json ├── .gitignore ├── README ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── api ├── README │ ├── role.png │ ├── user.png │ ├── database.png │ ├── user_role.png │ ├── permission.png │ └── role_permission.png ├── resetDB.sh ├── src │ ├── main │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── spring-devtools.properties │ │ │ ├── mapper │ │ │ │ ├── AccountRoleMapper.xml │ │ │ │ ├── RolePermissionMapper.xml │ │ │ │ ├── RoleMapper.xml │ │ │ │ ├── PermissionMapper.xml │ │ │ │ └── AccountMapper.xml │ │ │ ├── application-test.yml │ │ │ ├── application-prod.yml │ │ │ ├── application-dev.yml │ │ │ ├── application.yml │ │ │ └── banner.txt │ │ └── java │ │ │ └── com │ │ │ └── zoctan │ │ │ └── api │ │ │ ├── service │ │ │ ├── RolePermissionService.java │ │ │ ├── AccountRoleService.java │ │ │ ├── PermissionService.java │ │ │ ├── impl │ │ │ │ ├── RolePermissionServiceImpl.java │ │ │ │ ├── AccountRoleServiceImpl.java │ │ │ │ ├── PermissionServiceImpl.java │ │ │ │ ├── AccountDetailsServiceImpl.java │ │ │ │ └── RoleServiceImpl.java │ │ │ ├── RoleService.java │ │ │ └── AccountService.java │ │ │ ├── entity │ │ │ ├── Handle.java │ │ │ ├── Resource.java │ │ │ ├── AccountRole.java │ │ │ ├── RolePermission.java │ │ │ ├── Permission.java │ │ │ ├── Account.java │ │ │ └── Role.java │ │ │ ├── dto │ │ │ ├── AccountWithRolePermission.java │ │ │ ├── AccountWithRole.java │ │ │ ├── RoleWithResource.java │ │ │ ├── RoleWithPermission.java │ │ │ └── AccountDto.java │ │ │ ├── core │ │ │ ├── mapper │ │ │ │ └── MyMapper.java │ │ │ ├── cache │ │ │ │ └── CacheExpire.java │ │ │ ├── exception │ │ │ │ ├── ResourcesNotFoundException.java │ │ │ │ └── ServiceException.java │ │ │ ├── jwt │ │ │ │ └── JwtConfigurationProperties.java │ │ │ ├── rsa │ │ │ │ └── RsaConfigurationProperties.java │ │ │ ├── jasypt │ │ │ │ └── MyEncryptablePropertyDetector.java │ │ │ ├── response │ │ │ │ ├── ResultCode.java │ │ │ │ ├── Result.java │ │ │ │ └── ResultGenerator.java │ │ │ ├── constant │ │ │ │ └── ProjectConstant.java │ │ │ ├── config │ │ │ │ ├── ValidatorConfig.java │ │ │ │ ├── Swagger2Config.java │ │ │ │ ├── JasyptConfig.java │ │ │ │ ├── WebMvcConfig.java │ │ │ │ └── WebSecurityConfig.java │ │ │ └── service │ │ │ │ └── Service.java │ │ │ ├── mapper │ │ │ ├── RolePermissionMapper.java │ │ │ ├── RoleMapper.java │ │ │ ├── AccountRoleMapper.java │ │ │ ├── PermissionMapper.java │ │ │ └── AccountMapper.java │ │ │ ├── util │ │ │ ├── UrlUtils.java │ │ │ ├── ContextUtils.java │ │ │ ├── AssertUtils.java │ │ │ ├── JsonUtils.java │ │ │ └── AesUtils.java │ │ │ ├── controller │ │ │ ├── AccountRoleController.java │ │ │ ├── PermissionController.java │ │ │ └── RoleController.java │ │ │ ├── filter │ │ │ ├── MyAuthenticationEntryPoint.java │ │ │ └── RequestWrapper.java │ │ │ ├── Application.java │ │ │ └── aspect │ │ │ └── ControllerLogAspect.java │ └── test │ │ ├── resources │ │ ├── generator │ │ │ └── template │ │ │ │ ├── service.ftl │ │ │ │ ├── service-impl.ftl │ │ │ │ ├── controller-restful.ftl │ │ │ │ └── controller.ftl │ │ └── sql │ │ │ └── dev │ │ │ ├── role.sql │ │ │ ├── account_role.sql │ │ │ ├── role_permission.sql │ │ │ ├── permission.sql │ │ │ └── account.sql │ │ └── java │ │ ├── com │ │ └── tsbtv │ │ │ └── report │ │ │ ├── WithCustomUser.java │ │ │ ├── WithCustomSecurityContextFactory.java │ │ │ └── BaseControllerTest.java │ │ ├── PasswordEncryptor.java │ │ ├── JasyptStringEncryptor.java │ │ └── RsaEncryptor.java ├── .gitignore └── README.md ├── README-zh.md └── README.md /admin/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | vue/\.idea/ 3 | 4 | vue/package-lock\.json 5 | -------------------------------------------------------------------------------- /README/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/README/1.png -------------------------------------------------------------------------------- /README/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/README/2.png -------------------------------------------------------------------------------- /README/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/README/3.png -------------------------------------------------------------------------------- /README/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/README/4.png -------------------------------------------------------------------------------- /admin/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /admin/test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /admin/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/admin/favicon.ico -------------------------------------------------------------------------------- /api/README/role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/api/README/role.png -------------------------------------------------------------------------------- /api/README/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/api/README/user.png -------------------------------------------------------------------------------- /admin/src/router/_import_production.js: -------------------------------------------------------------------------------- 1 | module.exports = file => () => import('@/views/' + file + '.vue') 2 | -------------------------------------------------------------------------------- /admin/test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /api/README/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/api/README/database.png -------------------------------------------------------------------------------- /api/README/user_role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/api/README/user_role.png -------------------------------------------------------------------------------- /api/README/permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/api/README/permission.png -------------------------------------------------------------------------------- /api/README/role_permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/api/README/role_permission.png -------------------------------------------------------------------------------- /admin/src/assets/401Images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/admin/src/assets/401Images/401.gif -------------------------------------------------------------------------------- /admin/src/assets/404Images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/admin/src/assets/404Images/404.png -------------------------------------------------------------------------------- /admin/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | BASE_API: '"https://xx.com"' 5 | } 6 | -------------------------------------------------------------------------------- /admin/src/assets/404Images/404cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zoctan/spring-boot-vue-admin/HEAD/admin/src/assets/404Images/404cloud.png -------------------------------------------------------------------------------- /api/resetDB.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | for i in $(find src/test/resources/dev/sql/*.sql) ; do 3 | mysql -uroot -proot admin_dev < ${i}; 4 | done 5 | -------------------------------------------------------------------------------- /admin/src/router/_import_development.js: -------------------------------------------------------------------------------- 1 | module.exports = file => require('@/views/' + file + '.vue').default // web-loader at least v13.0.0+ 2 | -------------------------------------------------------------------------------- /admin/src/utils/hasPermission.js: -------------------------------------------------------------------------------- 1 | import store from '../store' 2 | 3 | export function hasPermission(permission) { 4 | return store.getters.permissionCodeList.indexOf(permission) >= 0 5 | } 6 | -------------------------------------------------------------------------------- /admin/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /admin/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /* 改变主题色变量 */ 2 | $--color-primary: #409EFF; 3 | 4 | /* 改变 icon 字体路径变量,必需 */ 5 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 6 | 7 | @import "~element-ui/packages/theme-chalk/src/index"; 8 | -------------------------------------------------------------------------------- /admin/config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"', 7 | BASE_API: '"http://localhost:8080"' 8 | }) 9 | -------------------------------------------------------------------------------- /admin/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"' 8 | }) 9 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | 3 | export { default as Sidebar } from './Sidebar/index' 4 | 5 | export { default as Levelbar } from './Levelbar' 6 | 7 | export { default as AppMain } from './AppMain' 8 | -------------------------------------------------------------------------------- /api/src/main/resources/META-INF/spring-devtools.properties: -------------------------------------------------------------------------------- 1 | # devtools热重启导致mapper出错的解决 2 | # https://github.com/abel533/MyBatis-Spring-Boot#spring-devtools-%E9%85%8D%E7%BD%AE 3 | restart.include.mapper=/mapper-[\\w-\\.]+jar 4 | restart.include.pagehelper=/pagehelper-[\\w-\\.]+jar -------------------------------------------------------------------------------- /admin/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | /test/e2e/reports/ 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /admin/.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 | -------------------------------------------------------------------------------- /admin/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import IconSvg from '@/components/Icon-svg'// svg组件 3 | 4 | // 注册全局组件 5 | Vue.component('icon-svg', IconSvg) 6 | 7 | const requireAll = requireContext => requireContext.keys().map(requireContext) 8 | const req = require.context('./svg', false, /\.svg$/) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ADMIN 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/RolePermissionService.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service; 2 | 3 | import com.zoctan.api.core.service.Service; 4 | import com.zoctan.api.entity.RolePermission; 5 | 6 | /** 7 | * @author Zoctan 8 | * @date 2018/06/09 9 | */ 10 | public interface RolePermissionService extends Service {} 11 | -------------------------------------------------------------------------------- /api/src/test/resources/generator/template/service.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.service; 2 | 3 | import ${basePackage}.entity.${modelNameUpperCamel}; 4 | import ${basePackage}.core.service.Service; 5 | 6 | /** 7 | * @author ${author} 8 | * @date ${date} 9 | */ 10 | public interface ${modelNameUpperCamel}Service extends Service<${modelNameUpperCamel}> { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/entity/Handle.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Transient; 6 | 7 | /** 8 | * @author Zoctan 9 | * @date 2018/10/16 10 | */ 11 | @Data 12 | public class Handle { 13 | /** 对应权限id */ 14 | @Transient private Long id; 15 | 16 | /** 操作名称 */ 17 | @Transient private String handle; 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/entity/Resource.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Transient; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Zoctan 10 | * @date 2018/10/16 11 | */ 12 | @Data 13 | public class Resource { 14 | @Transient private String resource; 15 | 16 | @Transient private List handleList; 17 | } 18 | -------------------------------------------------------------------------------- /admin/src/utils/token.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Account-Token' 4 | // 有效期 1 天 5 | const expires = 1 6 | 7 | export function getToken() { 8 | return Cookies.get(TokenKey) 9 | } 10 | 11 | export function setToken(token) { 12 | return Cookies.set(TokenKey, token, { expires: expires }) 13 | } 14 | 15 | export function removeToken() { 16 | return Cookies.remove(TokenKey) 17 | } 18 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | \.idea/ 25 | 26 | target/ 27 | -------------------------------------------------------------------------------- /admin/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import account from './modules/account' 5 | import permission from './modules/permission' 6 | import getters from './getters' 7 | 8 | Vue.use(Vuex) 9 | 10 | const store = new Vuex.Store({ 11 | modules: { 12 | app, 13 | account, 14 | permission 15 | }, 16 | getters 17 | }) 18 | 19 | export default store 20 | -------------------------------------------------------------------------------- /admin/test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /admin/src/components/Icon-svg/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/AccountRoleService.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service; 2 | 3 | import com.zoctan.api.core.service.Service; 4 | import com.zoctan.api.entity.AccountRole; 5 | 6 | /** 7 | * @author Zoctan 8 | * @date 2018/06/09 9 | */ 10 | public interface AccountRoleService extends Service { 11 | /** 12 | * 更新用户角色 13 | * 14 | * @param accountRole 用户角色 15 | */ 16 | void updateRoleIdByAccountId(AccountRole accountRole); 17 | } 18 | -------------------------------------------------------------------------------- /admin/.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 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /admin/README.md: -------------------------------------------------------------------------------- 1 | # admin 2 | 3 | 报料小程序后台 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | 20 | # run unit tests 21 | npm run unit 22 | 23 | # run e2e tests 24 | npm run e2e 25 | 26 | # run all tests 27 | npm test 28 | ``` 29 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/dto/AccountWithRolePermission.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.dto; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | import javax.persistence.Transient; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/10/16 12 | */ 13 | @EqualsAndHashCode(callSuper = true) 14 | @Data 15 | public class AccountWithRolePermission extends AccountWithRole { 16 | /** 用户的角色对应的权限code */ 17 | @Transient private List permissionCodeList; 18 | } 19 | -------------------------------------------------------------------------------- /admin/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 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/dto/AccountWithRole.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.dto; 2 | 3 | import com.zoctan.api.entity.Account; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import javax.persistence.Transient; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/10/16 12 | */ 13 | @EqualsAndHashCode(callSuper = true) 14 | @Data 15 | public class AccountWithRole extends Account { 16 | /** 用户的角色Id */ 17 | @Transient private Long roleId; 18 | 19 | /** 用户的角色名 */ 20 | @Transient private String roleName; 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/mapper/MyMapper.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.mapper; 2 | 3 | import tk.mybatis.mapper.common.BaseMapper; 4 | import tk.mybatis.mapper.common.ConditionMapper; 5 | import tk.mybatis.mapper.common.IdsMapper; 6 | import tk.mybatis.mapper.common.special.InsertListMapper; 7 | 8 | /** 9 | * 定制版 MyBatis Mapper 插件接口,如需其他接口参考官方文档自行添加 10 | * 11 | * @author Zoctan 12 | * @date 2018/05/27 13 | */ 14 | public interface MyMapper 15 | extends BaseMapper, ConditionMapper, IdsMapper, InsertListMapper {} 16 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/dto/RoleWithResource.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.dto; 2 | 3 | import com.zoctan.api.entity.Resource; 4 | import com.zoctan.api.entity.Role; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | import javax.persistence.Transient; 9 | import java.util.List; 10 | 11 | /** 12 | * @author Zoctan 13 | * @date 2018/10/16 14 | */ 15 | @EqualsAndHashCode(callSuper = true) 16 | @Data 17 | public class RoleWithResource extends Role { 18 | /** 角色对应的权限 */ 19 | @Transient private List resourceList; 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/entity/AccountRole.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Id; 7 | import javax.persistence.Table; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/06/09 12 | */ 13 | @Data 14 | @Table(name = "account_role") 15 | public class AccountRole { 16 | /** 用户Id */ 17 | @Id 18 | @Column(name = "account_id") 19 | private Long accountId; 20 | 21 | /** 角色Id */ 22 | @Column(name = "role_id") 23 | private Long roleId; 24 | } 25 | -------------------------------------------------------------------------------- /api/src/test/java/com/tsbtv/report/WithCustomUser.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api; 2 | 3 | import org.springframework.security.test.context.support.WithSecurityContext; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | /** 9 | * 访问控制器时以某用户已登录状态操作 10 | * 11 | * @author Zoctan 12 | * @date 2018/11/29 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @WithSecurityContext(factory = WithCustomSecurityContextFactory.class) 16 | public @interface WithCustomUser { 17 | String name() default "admin"; 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/entity/RolePermission.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Id; 7 | import javax.persistence.Table; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/06/09 12 | */ 13 | @Data 14 | @Table(name = "role_permission") 15 | public class RolePermission { 16 | /** 角色Id */ 17 | @Id 18 | @Column(name = "role_id") 19 | private Long roleId; 20 | 21 | /** 权限Id */ 22 | @Column(name = "permission_id") 23 | private Long permissionId; 24 | } 25 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/dto/RoleWithPermission.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.dto; 2 | 3 | import com.zoctan.api.entity.Role; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author Zoctan 9 | * @date 2018/10/16 10 | */ 11 | public class RoleWithPermission extends Role { 12 | /** 角色对应的权限Id列表 */ 13 | private List permissionIdList; 14 | 15 | public List getPermissionIdList() { 16 | return this.permissionIdList; 17 | } 18 | 19 | public void setPermissionIdList(final List permissionIdList) { 20 | this.permissionIdList = permissionIdList; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/cache/CacheExpire.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.cache; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * 缓存过期注解 9 | * 10 | * @author Zoctan 11 | * @date 2018/07/11 12 | */ 13 | @Inherited 14 | @Documented 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target({ElementType.METHOD, ElementType.TYPE}) 17 | public @interface CacheExpire { 18 | /** 过期时间,默认 60s */ 19 | @AliasFor("expire") 20 | long value() default 60L; 21 | 22 | /** 过期时间,默认 60s */ 23 | @AliasFor("value") 24 | long expire() default 60L; 25 | } 26 | -------------------------------------------------------------------------------- /admin/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | 4 | token: state => state.account.token, 5 | accountId: state => state.account.accountId, 6 | email: state => state.account.email, 7 | name: state => state.account.name, 8 | loginTime: state => state.account.loginTime, 9 | registerTime: state => state.account.registerTime, 10 | roleName: state => state.account.roleName, 11 | permissionCodeList: state => state.account.permissionCodeList, 12 | 13 | permissionRouters: state => state.permission.routers, 14 | addRouters: state => state.permission.addRouters 15 | } 16 | export default getters 17 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /admin/test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/entity/Permission.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/06/09 12 | */ 13 | @Data 14 | public class Permission { 15 | /** 权限Id */ 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | /** 权限对应的资源 */ 21 | private String resource; 22 | 23 | /** 权限的代码/通配符,对应代码中@hasAuthority(xx) */ 24 | private String code; 25 | 26 | /** 对应的资源操作 */ 27 | private String handle; 28 | } 29 | -------------------------------------------------------------------------------- /admin/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/mapper/RolePermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.mapper; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import com.zoctan.api.core.mapper.MyMapper; 5 | import com.zoctan.api.entity.RolePermission; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/06/09 12 | */ 13 | public interface RolePermissionMapper extends MyMapper { 14 | /** 15 | * 保存角色以及对应的权限ID 16 | * 17 | * @param roleId 角色ID 18 | * @param permissionIdList 权限ID列表 19 | */ 20 | void saveRolePermission( 21 | @Param("roleId") Long roleId, @Param("permissionIdList") List permissionIdList); 22 | } 23 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/exception/ResourcesNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.exception; 2 | 3 | /** 4 | * 资源没找到异常(更新和删除都需先确认存在才操作) 5 | * 6 | * @author Zoctan 7 | * @date 2018/07/20 8 | */ 9 | public class ResourcesNotFoundException extends RuntimeException { 10 | private static final String DEFAULT_MESSAGE = "资源不存在"; 11 | 12 | public ResourcesNotFoundException() { 13 | super(DEFAULT_MESSAGE); 14 | } 15 | 16 | public ResourcesNotFoundException(final String message) { 17 | super(message); 18 | } 19 | 20 | public ResourcesNotFoundException(final String message, final Throwable cause) { 21 | super(message, cause); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/mapper/RoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.mapper; 2 | 3 | import com.zoctan.api.core.mapper.MyMapper; 4 | import com.zoctan.api.dto.RoleWithResource; 5 | import com.zoctan.api.entity.Role; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author Zoctan 12 | * @date 2018/06/09 13 | */ 14 | public interface RoleMapper extends MyMapper { 15 | /** 16 | * 获取所有角色以及对应的权限 17 | * 18 | * @return 角色可控资源列表 19 | */ 20 | List listRoles(); 21 | 22 | /** 23 | * 按角色Id更新修改时间 24 | * 25 | * @param id 角色Id 26 | */ 27 | void updateTimeById(@Param("id") Long id); 28 | } 29 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/PermissionService.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service; 2 | 3 | import com.zoctan.api.core.service.Service; 4 | import com.zoctan.api.entity.Permission; 5 | import com.zoctan.api.entity.Resource; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/05/17 12 | */ 13 | public interface PermissionService extends Service { 14 | /** 15 | * 找到所有权限可控资源 16 | * 17 | * @return 资源列表 18 | */ 19 | List listResourceWithHandle(); 20 | 21 | /** 22 | * 找到角色权限可控资源 23 | * 24 | * @param roleId 角色id 25 | * @return 资源列表 26 | */ 27 | List listRoleWithResourceByRoleId(Long roleId); 28 | } 29 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/mapper/AccountRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.mapper; 2 | 3 | import com.zoctan.api.core.mapper.MyMapper; 4 | import com.zoctan.api.entity.AccountRole; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.apache.ibatis.annotations.Update; 7 | 8 | /** 9 | * @author Zoctan 10 | * @date 2018/06/09 11 | */ 12 | public interface AccountRoleMapper extends MyMapper { 13 | 14 | /** 15 | * 更新用户角色 16 | * 17 | * @param accountRole 用户角色 18 | */ 19 | @Update( 20 | "UPDATE account_role SET role_id = #{accountRole.roleId} WHERE account_id = #{accountRole.accountId}") 21 | void updateRoleIdByAccountId(@Param("accountRole") AccountRole accountRole); 22 | } 23 | -------------------------------------------------------------------------------- /admin/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | // 有效期 1 天 4 | const expires = 1 5 | 6 | const app = { 7 | state: { 8 | sidebar: { 9 | opened: !+Cookies.get('sidebarStatus', { expires: expires }) 10 | } 11 | }, 12 | mutations: { 13 | TOGGLE_SIDEBAR: state => { 14 | if (state.sidebar.opened) { 15 | Cookies.set('sidebarStatus', 1, { expires: expires }) 16 | } else { 17 | Cookies.set('sidebarStatus', 0, { expires: expires }) 18 | } 19 | state.sidebar.opened = !state.sidebar.opened 20 | } 21 | }, 22 | actions: { 23 | ToggleSideBar: ({ commit }) => { 24 | commit('TOGGLE_SIDEBAR') 25 | } 26 | } 27 | } 28 | 29 | export default app 30 | -------------------------------------------------------------------------------- /admin/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | 23 | 34 | -------------------------------------------------------------------------------- /admin/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | 37 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/util/UrlUtils.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.util; 2 | 3 | import javax.servlet.ServletRequest; 4 | import javax.servlet.http.HttpServletRequest; 5 | 6 | /** 7 | * Url工具 8 | * 9 | * @author Zoctan 10 | * @date 2018/07/13 11 | */ 12 | public class UrlUtils { 13 | private UrlUtils() {} 14 | 15 | /** 16 | * 请求的相对路径 /account/list 17 | * 18 | * @param request request 19 | * @return 相对路径 20 | */ 21 | public static String getMappingUrl(final ServletRequest request) { 22 | return getMappingUrl((HttpServletRequest) request); 23 | } 24 | 25 | public static String getMappingUrl(final HttpServletRequest request) { 26 | return request.getRequestURI().substring(request.getContextPath().length()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /api/src/main/resources/mapper/AccountRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | UPDATE account_role 11 | SET role_id = #{accountRole.roleId} 12 | WHERE account_id = #{accountRole.accountId} 13 | 14 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/entity/Account.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import java.sql.Timestamp; 9 | 10 | /** 11 | * @author Zoctan 12 | * @date 2018/06/09 13 | */ 14 | @Data 15 | public class Account { 16 | /** 用户Id */ 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | 21 | /** 邮箱 */ 22 | private String email; 23 | 24 | /** 用户名 */ 25 | private String name; 26 | 27 | /** 密码 */ 28 | private String password; 29 | 30 | /** 注册时间 */ 31 | private Timestamp registerTime; 32 | 33 | /** 上一次登录时间 */ 34 | private Timestamp loginTime; 35 | } 36 | -------------------------------------------------------------------------------- /admin/src/icons/svg/name.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/util/ContextUtils.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.util; 2 | 3 | import org.springframework.web.context.request.RequestContextHolder; 4 | import org.springframework.web.context.request.ServletRequestAttributes; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | /** 9 | * 上下文工具 10 | * 11 | * @author Zoctan 12 | * @date 2018/07/20 13 | */ 14 | public class ContextUtils { 15 | private ContextUtils() {} 16 | 17 | /** 18 | * 获取 request 19 | * 20 | * @return request 21 | */ 22 | public static HttpServletRequest getRequest() { 23 | final ServletRequestAttributes attributes = 24 | ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()); 25 | return attributes == null ? null : attributes.getRequest(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/impl/RolePermissionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.transaction.annotation.Transactional; 5 | import com.zoctan.api.core.service.AbstractService; 6 | import com.zoctan.api.mapper.RolePermissionMapper; 7 | import com.zoctan.api.entity.RolePermission; 8 | import com.zoctan.api.service.RolePermissionService; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author Zoctan 14 | * @date 2018/06/09 15 | */ 16 | @Service 17 | @Transactional(rollbackFor = Exception.class) 18 | public class RolePermissionServiceImpl extends AbstractService 19 | implements RolePermissionService { 20 | @Resource private RolePermissionMapper rolePermissionMapper; 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/resources/mapper/RolePermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | insert into role_permission (role_id, permission_id) 11 | values 12 | 13 | (#{roleId}, #{item}) 14 | 15 | 16 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/jwt/JwtConfigurationProperties.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.jwt; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.time.Duration; 8 | 9 | /** 10 | * Json web token 配置 11 | * 12 | * @author Zoctan 13 | * @date 2018/06/09 14 | */ 15 | @Data 16 | @Component 17 | @ConfigurationProperties(prefix = "jwt") 18 | public class JwtConfigurationProperties { 19 | /** claim authorities key */ 20 | private String claimKeyAuth; 21 | /** token 前缀 */ 22 | private String tokenType; 23 | /** 请求头或请求参数的key */ 24 | private String header; 25 | /** 管理后台过期时间 */ 26 | private Duration adminExpireTime; 27 | /** 小程序前台过期时间 */ 28 | private Duration wechatExpireTime; 29 | } 30 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.validation.constraints.NotEmpty; 10 | import java.sql.Timestamp; 11 | 12 | /** 13 | * @author Zoctan 14 | * @date 2018/06/09 15 | */ 16 | @Data 17 | public class Role { 18 | /** 角色Id */ 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | 23 | /** 角色名称 */ 24 | @NotEmpty(message = "角色名不能为空") 25 | private String name; 26 | 27 | /** 创建时间 */ 28 | @Column(name = "create_time") 29 | private Timestamp createTime; 30 | 31 | /** 修改时间 */ 32 | @Column(name = "update_time") 33 | private Timestamp updateTime; 34 | } 35 | -------------------------------------------------------------------------------- /api/src/test/resources/generator/template/service-impl.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.service.impl; 2 | 3 | import ${basePackage}.mapper.${modelNameUpperCamel}Mapper; 4 | import ${basePackage}.entity.${modelNameUpperCamel}; 5 | import ${basePackage}.service.${modelNameUpperCamel}Service; 6 | import ${basePackage}.core.service.AbstractService; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author ${author} 14 | * @date ${date} 15 | */ 16 | @Service 17 | @Transactional(rollbackFor = Exception.class) 18 | public class ${modelNameUpperCamel}ServiceImpl extends AbstractService<${modelNameUpperCamel}> implements ${modelNameUpperCamel}Service { 19 | @Resource 20 | private ${modelNameUpperCamel}Mapper ${modelNameLowerCamel}Mapper; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /admin/test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | testPathIgnorePatterns: [ 18 | '/test/e2e' 19 | ], 20 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 21 | setupFiles: ['/test/unit/setup'], 22 | mapCoverage: true, 23 | coverageDirectory: '/test/unit/coverage', 24 | collectCoverageFrom: [ 25 | 'src/**/*.{js,vue}', 26 | '!src/main.js', 27 | '!src/router/index.js', 28 | '!**/node_modules/**' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/RoleService.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service; 2 | 3 | import com.zoctan.api.core.service.Service; 4 | import com.zoctan.api.dto.RoleWithPermission; 5 | import com.zoctan.api.dto.RoleWithResource; 6 | import com.zoctan.api.entity.Role; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author Zoctan 12 | * @date 2018/06/09 13 | */ 14 | public interface RoleService extends Service { 15 | 16 | /** 17 | * 新建角色 18 | * 19 | * @param roleWithPermission 带权限列表的角色 20 | */ 21 | void save(RoleWithPermission roleWithPermission); 22 | 23 | /** 24 | * 更新角色 25 | * 26 | * @param roleWithPermission 带权限列表的角色 27 | */ 28 | void update(RoleWithPermission roleWithPermission); 29 | 30 | /** 31 | * 获取所有角色以及对应的权限 32 | * 33 | * @return 角色可控资源列表 34 | */ 35 | List listRoleWithPermission(); 36 | } 37 | -------------------------------------------------------------------------------- /admin/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import ElementUI from 'element-ui' 3 | import './styles/element-variables.scss' 4 | import App from './App' 5 | import router from './router' 6 | import store from './store' 7 | import '@/icons' // icon 8 | import '@/permission' // 权限 9 | import { default as request } from './utils/request' 10 | import { hasPermission } from './utils/hasPermission' 11 | import lang from 'element-ui/lib/locale/lang/zh-CN' 12 | import locale from 'element-ui/lib/locale' 13 | 14 | // 设置语言 15 | locale.use(lang) 16 | 17 | Vue.use(ElementUI, { 18 | size: 'small' 19 | }) 20 | 21 | // 全局的常量 22 | Vue.prototype.request = request 23 | Vue.prototype.hasPermission = hasPermission 24 | 25 | // 生产环境时自动设置为 false 以阻止 web 在启动时生成生产提示 26 | Vue.config.productionTip = false 27 | 28 | new Vue({ 29 | el: '#app', 30 | router, 31 | store, 32 | template: '', 33 | components: { App } 34 | }) 35 | -------------------------------------------------------------------------------- /api/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | datasource: 6 | url: jdbc:mysql://localhost:3306/admin_test?useUnicode=true&useSSL=false&allowMultiQueries=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=UTC 7 | username: root 8 | password: root 9 | driver-class-name: com.mysql.cj.jdbc.Driver 10 | cache: 11 | type: redis 12 | redis: 13 | key-prefix: admin_test 14 | time-to-live: 60s 15 | redis: 16 | database: 0 17 | host: 127.0.0.1 18 | port: 6379 19 | #password: root 20 | jedis.pool: 21 | max-active: 8 22 | max-wait: -1ms 23 | max-idle: 8 24 | min-idle: 0 25 | 26 | logging: 27 | level.com.zoctan.api: info 28 | 29 | jwt: 30 | admin-expire-time: 7d 31 | wechat-expire-time: 30d 32 | claim-key-auth: auth 33 | header: Authorization 34 | token-type: Bearer 35 | -------------------------------------------------------------------------------- /admin/test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /api/src/test/java/PasswordEncryptor.java: -------------------------------------------------------------------------------- 1 | import com.zoctan.api.Application; 2 | import org.junit.Test; 3 | import org.junit.runner.RunWith; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | /** 10 | * @author Zoctan 11 | * @date 2018/05/27 12 | */ 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest(classes = Application.class) 15 | public class PasswordEncryptor { 16 | 17 | @Autowired private PasswordEncoder passwordEncoder; 18 | 19 | @Test 20 | public void encode() { 21 | final String admin = this.passwordEncoder.encode("admin123"); 22 | final String user = this.passwordEncoder.encode("editor123"); 23 | System.err.println("admin password = " + admin); 24 | System.err.println("editor password = " + user); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/impl/AccountRoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service.impl; 2 | 3 | import com.zoctan.api.core.service.AbstractService; 4 | import com.zoctan.api.entity.AccountRole; 5 | import com.zoctan.api.mapper.AccountRoleMapper; 6 | import com.zoctan.api.service.AccountRoleService; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author Zoctan 14 | * @date 2018/06/09 15 | */ 16 | @Service 17 | @Transactional(rollbackFor = Exception.class) 18 | public class AccountRoleServiceImpl extends AbstractService 19 | implements AccountRoleService { 20 | @Resource private AccountRoleMapper accountRoleMapper; 21 | 22 | @Override 23 | public void updateRoleIdByAccountId(final AccountRole accountRole) { 24 | this.accountRoleMapper.updateRoleIdByAccountId(accountRole); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/mapper/PermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.mapper; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.apache.ibatis.annotations.Select; 5 | import com.zoctan.api.core.mapper.MyMapper; 6 | import com.zoctan.api.entity.Permission; 7 | import com.zoctan.api.entity.Resource; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author Zoctan 13 | * @date 2018/06/09 14 | */ 15 | public interface PermissionMapper extends MyMapper { 16 | /** 17 | * 找到所有权限可控资源 18 | * 19 | * @return 资源列表 20 | */ 21 | List listResourceWithHandle(); 22 | 23 | /** 24 | * 找到所有权限可控资源 25 | * 26 | * @param roleId 角色id 27 | * @return 资源列表 28 | */ 29 | List listRoleWithResourceByRoleId(@Param("roleId") Long roleId); 30 | 31 | /** 32 | * 获取所有权限代码 33 | * 34 | * @return 代码列表 35 | */ 36 | @Select("SELECT p.code FROM `permission` p") 37 | List listAllCode(); 38 | } 39 | -------------------------------------------------------------------------------- /api/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | datasource: 6 | url: jdbc:mysql://localhost:3306/admin_prod?useUnicode=true&useSSL=false&allowMultiQueries=true&characterEncoding=utf-8 7 | # root 8 | username: MyEnc({btTSNb3PH7saU2yQ7FeCsQ==}) 9 | # root 10 | password: MyEnc({VuLn6kkmZXt1402C9w9xUA==}) 11 | driver-class-name: com.mysql.cj.jdbc.Driver 12 | cache: 13 | type: redis 14 | redis: 15 | key-prefix: admin_prod 16 | time-to-live: 60s 17 | redis: 18 | database: 0 19 | host: 127.0.0.1 20 | port: 6379 21 | # root 22 | password: MyEnc({eCOS8Sk9b/kWt2FK0QFA9g==}) 23 | jedis.pool: 24 | max-active: 8 25 | max-wait: -1ms 26 | max-idle: 8 27 | min-idle: 0 28 | 29 | logging: 30 | level.com.zoctan.api: warn 31 | 32 | jwt: 33 | admin-expire-time: 7d 34 | wechat-expire-time: 30d 35 | claim-key-auth: auth 36 | header: Authorization 37 | token-type: Bearer 38 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/rsa/RsaConfigurationProperties.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.rsa; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * RSA 配置 9 | * 10 | * @author Zoctan 11 | * @date 2018/07/20 12 | */ 13 | @Data 14 | @Component 15 | @ConfigurationProperties(prefix = "rsa") 16 | public class RsaConfigurationProperties { 17 | /** 私钥位置 */ 18 | private String privateKeyPath; 19 | /** 公钥位置 */ 20 | private String publicKeyPath; 21 | /** 使用文件还是直接使用字符串 */ 22 | private boolean useFile; 23 | /** 私钥 */ 24 | private String privateKey; 25 | /** 公钥 */ 26 | private String publicKey; 27 | 28 | private String publicKeyHead = "-----BEGIN PUBLIC KEY-----"; 29 | private String publicKeyTail = "-----END PUBLIC KEY-----"; 30 | private String privateKeyHead = "-----BEGIN PRIVATE KEY-----"; 31 | private String privateKeyTail = "-----END PRIVATE KEY-----"; 32 | } 33 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/dto/AccountDto.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.dto; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotEmpty; 8 | import javax.validation.constraints.Size; 9 | import java.sql.Timestamp; 10 | 11 | /** 12 | * @author Zoctan 13 | * @date 2018/06/09 14 | */ 15 | @Data 16 | public class AccountDto { 17 | /** 用户Id */ 18 | private Long id; 19 | 20 | /** 邮箱 */ 21 | @NotEmpty(message = "邮箱不能为空") 22 | @Email 23 | private String email; 24 | 25 | /** 用户名 */ 26 | @NotEmpty(message = "用户名不能为空") 27 | @Size(min = 3, message = "用户名长度不能小于3") 28 | private String name; 29 | 30 | /** 密码 */ 31 | @JSONField(serialize = false) 32 | @NotEmpty(message = "密码不能为空") 33 | @Size(min = 6, message = "密码长度不能小于6") 34 | private String password; 35 | 36 | /** 注册时间 */ 37 | private Timestamp registerTime; 38 | 39 | /** 上一次登录时间 */ 40 | private Timestamp loginTime; 41 | 42 | private Long roleId; 43 | } 44 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.exception; 2 | 3 | import com.zoctan.api.core.response.ResultCode; 4 | 5 | /** 6 | * Service 异常 7 | * 8 | * @author Zoctan 9 | * @date 2018/05/27 10 | */ 11 | public class ServiceException extends RuntimeException { 12 | private ResultCode resultCode; 13 | 14 | public ServiceException(final String message) { 15 | super(message); 16 | } 17 | 18 | public ServiceException(final String message, final Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public ServiceException(final ResultCode resultCode, final String message) { 23 | super(message); 24 | this.resultCode = resultCode; 25 | } 26 | 27 | public ServiceException(final ResultCode resultCode) { 28 | super(resultCode.getReason()); 29 | this.resultCode = resultCode; 30 | } 31 | 32 | public ResultCode getResultCode() { 33 | return this.resultCode; 34 | } 35 | 36 | public void setResultCode(final ResultCode resultCode) { 37 | this.resultCode = resultCode; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /admin/src/icons/svg/permission.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/src/test/java/JasyptStringEncryptor.java: -------------------------------------------------------------------------------- 1 | import com.zoctan.api.Application; 2 | import org.jasypt.encryption.StringEncryptor; 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import javax.annotation.Resource; 10 | 11 | /** 12 | * jasypt 用于加密配置文件 https://github.com/ulisesbocchio/jasypt-spring-boot 13 | * 14 | * @author Zoctan 15 | * @date 2018/05/27 16 | */ 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest(classes = Application.class) 19 | public class JasyptStringEncryptor { 20 | 21 | @Resource(name = "myStringEncryptor") 22 | private StringEncryptor stringEncryptor; 23 | 24 | @Test 25 | public void encode() { 26 | final String name = this.stringEncryptor.encrypt("root"); 27 | final String password = this.stringEncryptor.encrypt("root"); 28 | 29 | System.err.println("name = " + name); 30 | System.err.println("password = " + password); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/util/AssertUtils.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.util; 2 | 3 | import com.zoctan.api.core.exception.ServiceException; 4 | import com.zoctan.api.core.response.ResultCode; 5 | 6 | /** 7 | * 断言工具 8 | * 9 | * @author Zoctan 10 | * @date 2018/11/29 11 | */ 12 | public class AssertUtils { 13 | public static void throwIf( 14 | final boolean statement, final ResultCode resultCode, final String message) { 15 | if (statement) { 16 | throw toThrow(resultCode, message); 17 | } 18 | } 19 | 20 | public static void throwIf( 21 | final boolean statement, final ResultCode resultCode, final Object... messages) { 22 | throwIf(statement, resultCode, resultCode.format(messages)); 23 | } 24 | 25 | public static RuntimeException toThrow(final ResultCode resultCode, final Object... messages) { 26 | return new ServiceException(resultCode, resultCode.format(messages)); 27 | } 28 | 29 | public static void asserts( 30 | final boolean statement, final ResultCode resultCode, final Object... messages) { 31 | throwIf(!statement, resultCode, messages); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /admin/src/api/role.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function listRoleWithPermission(params) { 4 | return request({ 5 | url: '/role/permission', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | 11 | export function list(params) { 12 | return request({ 13 | url: '/role', 14 | method: 'get', 15 | params 16 | }) 17 | } 18 | 19 | export function listResourcePermission(params) { 20 | return request({ 21 | url: '/permission', 22 | method: 'get', 23 | params 24 | }) 25 | } 26 | 27 | export function add(roleForm) { 28 | return request({ 29 | url: '/role', 30 | method: 'post', 31 | data: roleForm 32 | }) 33 | } 34 | 35 | export function update(roleForm) { 36 | return request({ 37 | url: '/role', 38 | method: 'put', 39 | data: roleForm 40 | }) 41 | } 42 | 43 | export function remove(roleId) { 44 | return request({ 45 | url: '/role/' + roleId, 46 | method: 'delete' 47 | }) 48 | } 49 | 50 | export function updateAccountRole(account) { 51 | return request({ 52 | url: '/account/role', 53 | method: 'put', 54 | data: account 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/jasypt/MyEncryptablePropertyDetector.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.jasypt; 2 | 3 | import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyDetector; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * 自定义被加密值的发现器 默认:ENC(abc) 自定义:MyEnc({abc}) 9 | * 10 | * @author Zoctan 11 | * @date 2018/07/20 12 | */ 13 | @Component 14 | public class MyEncryptablePropertyDetector implements EncryptablePropertyDetector { 15 | /** 前缀 */ 16 | private static final String PREFIX = "MyEnc({"; 17 | /** 后缀 */ 18 | private static final String SUFFIX = "})"; 19 | 20 | @Override 21 | public boolean isEncrypted(final String property) { 22 | if (StringUtils.isBlank(property)) { 23 | return false; 24 | } 25 | final String trimmedProperty = property.trim(); 26 | 27 | return trimmedProperty.startsWith(PREFIX) && trimmedProperty.endsWith(SUFFIX); 28 | } 29 | 30 | @Override 31 | public String unwrapEncryptedValue(final String property) { 32 | return property.substring(PREFIX.length(), property.length() - SUFFIX.length()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /admin/src/icons/svg/report.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/impl/PermissionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.transaction.annotation.Transactional; 5 | import com.zoctan.api.core.service.AbstractService; 6 | import com.zoctan.api.mapper.PermissionMapper; 7 | import com.zoctan.api.entity.Permission; 8 | import com.zoctan.api.service.PermissionService; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.List; 12 | 13 | /** 14 | * @author Zoctan 15 | * @date 2018/06/09 16 | */ 17 | @Service 18 | @Transactional(rollbackFor = Exception.class) 19 | public class PermissionServiceImpl extends AbstractService 20 | implements PermissionService { 21 | @Resource private PermissionMapper permissionMapper; 22 | 23 | @Override 24 | public List listResourceWithHandle() { 25 | return this.permissionMapper.listResourceWithHandle(); 26 | } 27 | 28 | @Override 29 | public List listRoleWithResourceByRoleId(Long roleId) { 30 | return this.permissionMapper.listRoleWithResourceByRoleId(roleId); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /admin/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * url地址 3 | * @param url 4 | * @returns {boolean} 5 | */ 6 | export function isValidateURL(url) { 7 | const reg = /^(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.,?'\\+&%$#=~_-]+))*$/ 8 | return reg.test(url) 9 | } 10 | 11 | /** 12 | * 小写字母 13 | * @param str 14 | * @returns {boolean} 15 | */ 16 | export function isValidateLowerCase(str) { 17 | const reg = /^[a-z]+$/ 18 | return reg.test(str) 19 | } 20 | 21 | /** 22 | * 大写字母 23 | * @param str 24 | * @returns {boolean} 25 | */ 26 | export function isValidateUpperCase(str) { 27 | const reg = /^[A-Z]+$/ 28 | return reg.test(str) 29 | } 30 | 31 | /** 32 | * 邮箱 33 | * @param email 34 | * @returns {boolean} 35 | */ 36 | export function isValidateEmail(email) { 37 | const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 38 | return reg.test(email) 39 | } 40 | 41 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/response/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.response; 2 | 3 | /** 4 | * 响应状态码枚举类 5 | * 6 | *

自定义业务异常 2*** 开始 7 | * 8 | *

原有类异常 4*** 开始 9 | * 10 | * @author Zoctan 11 | * @date 2018/07/14 12 | */ 13 | public enum ResultCode { 14 | SUCCEED_REQUEST_FAILED_RESULT(1000, "成功请求,但结果不是期望的成功结果"), 15 | 16 | FIND_FAILED(2000, "查询失败"), 17 | 18 | SAVE_FAILED(2001, "保存失败"), 19 | 20 | UPDATE_FAILED(2002, "更新失败"), 21 | 22 | DELETE_FAILED(2003, "删除失败"), 23 | 24 | DUPLICATE_NAME(2004, "账户名重复"), 25 | 26 | DATABASE_EXCEPTION(4001, "数据库异常"), 27 | 28 | UNAUTHORIZED_EXCEPTION(4002, "认证异常"), 29 | 30 | VIOLATION_EXCEPTION(4003, "验证异常"); 31 | 32 | private final int value; 33 | 34 | private final String reason; 35 | 36 | ResultCode(final int value, final String reason) { 37 | this.value = value; 38 | this.reason = reason; 39 | } 40 | 41 | public int getValue() { 42 | return this.value; 43 | } 44 | 45 | public String getReason() { 46 | return this.reason; 47 | } 48 | 49 | public String format(final Object... objects) { 50 | return objects.length > 0 ? String.format(this.getReason(), objects) : this.getReason(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /admin/src/icons/svg/log.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/response/Result.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.response; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | 7 | /** 8 | * @author Zoctan 9 | * @date 2018/07/15 10 | */ 11 | @ApiModel(value = "响应结果") 12 | public class Result { 13 | @ApiModelProperty(value = "状态码") 14 | private Integer code; 15 | 16 | @ApiModelProperty(value = "消息") 17 | private String message; 18 | 19 | @ApiModelProperty(value = "数据") 20 | private T data; 21 | 22 | @Override 23 | public String toString() { 24 | return JSON.toJSONString(this); 25 | } 26 | 27 | public Integer getCode() { 28 | return this.code; 29 | } 30 | 31 | public Result setCode(final Integer code) { 32 | this.code = code; 33 | return this; 34 | } 35 | 36 | public String getMessage() { 37 | return this.message; 38 | } 39 | 40 | public Result setMessage(final String message) { 41 | this.message = message; 42 | return this; 43 | } 44 | 45 | public T getData() { 46 | return this.data; 47 | } 48 | 49 | public Result setData(final T data) { 50 | this.data = data; 51 | return this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/mapper/AccountMapper.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.mapper; 2 | 3 | import com.zoctan.api.core.mapper.MyMapper; 4 | import com.zoctan.api.dto.AccountWithRole; 5 | import com.zoctan.api.dto.AccountWithRolePermission; 6 | import com.zoctan.api.entity.Account; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author Zoctan 14 | * @date 2018/06/09 15 | */ 16 | public interface AccountMapper extends MyMapper { 17 | /** 18 | * 获取所有用户以及对应角色 19 | * 20 | * @return 用户列表 21 | */ 22 | List listAllWithRole(); 23 | 24 | /** 25 | * 按微信小程序Id获取用户 26 | * 27 | * @return 用户 28 | */ 29 | Account findByWechatOpenId(@Param("openId") String openId); 30 | 31 | /** 32 | * 按条件获取用户 33 | * 34 | * @param params 参数 35 | * @return 用户列表 36 | */ 37 | List findWithRoleBy(final Map params); 38 | 39 | /** 40 | * 按条件查询用户信息 41 | * 42 | * @param params 参数 43 | * @return 用户 44 | */ 45 | AccountWithRolePermission findDetailBy(Map params); 46 | 47 | /** 48 | * 按用户名更新最后登陆时间 49 | * 50 | * @param name 用户名 51 | */ 52 | void updateLoginTimeByName(@Param("name") String name); 53 | } 54 | -------------------------------------------------------------------------------- /admin/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './mixin.scss'; 2 | 3 | body { 4 | -moz-osx-font-smoothing: grayscale; 5 | -webkit-font-smoothing: antialiased; 6 | text-rendering: optimizeLegibility; 7 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 8 | } 9 | 10 | html { 11 | box-sizing: border-box; 12 | } 13 | 14 | *, 15 | *:before, 16 | *:after { 17 | box-sizing: inherit; 18 | } 19 | 20 | a:focus, 21 | a:active { 22 | outline: none; 23 | } 24 | 25 | a, 26 | a:focus, 27 | a:hover { 28 | cursor: pointer; 29 | color: inherit; 30 | text-decoration: none; 31 | } 32 | 33 | .clearFix { 34 | &:after { 35 | visibility: hidden; 36 | display: block; 37 | font-size: 0; 38 | content: " "; 39 | clear: both; 40 | height: 0; 41 | } 42 | } 43 | 44 | //web router transition css 45 | .fade-enter-active, 46 | .fade-leave-active { 47 | transition: all .2s ease 48 | } 49 | 50 | .fade-enter, 51 | .fade-leave-active { 52 | opacity: 0; 53 | } 54 | 55 | //main-container全局样式 56 | .app-main { 57 | min-height: 100% 58 | } 59 | 60 | .app-container { 61 | padding: 20px; 62 | } 63 | 64 | .svg-icon { 65 | width: 1em; 66 | height: 1em; 67 | vertical-align: -0.15em; 68 | fill: currentColor; 69 | overflow: hidden; 70 | } 71 | 72 | -------------------------------------------------------------------------------- /admin/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 { getToken } from '@/utils/token' 6 | 7 | const whiteList = ['/login'] // 白名单,不需要登录的路由 8 | 9 | router.beforeEach((to, from, next) => { 10 | NProgress.start() // 开始Progress 11 | // 尝试获取cookie中的token 12 | if (getToken()) { 13 | // 有token 14 | if (to.path === '/login') { 15 | // 但下一跳是登陆页 16 | // 转到首页 17 | next({ path: '/' }) 18 | } else { 19 | // 下一跳不是登陆页 20 | // VUEX被清除,没有角色名 21 | if (store.getters.roleName === null) { 22 | // 重新获取用户信息 23 | store.dispatch('Detail').then(response => { 24 | // 生成路由 25 | store.dispatch('GenerateRoutes', response.data).then(() => { 26 | router.addRoutes(store.getters.addRouters) 27 | next({ ...to }) 28 | }) 29 | }) 30 | } else { 31 | next() 32 | } 33 | } 34 | } else { 35 | // 如果前往的路径是白名单内的,就可以直接前往 36 | if (whiteList.indexOf(to.path) !== -1) { 37 | next() 38 | } else { 39 | // 如果路径不是白名单内的,而且又没有登录,就转到登录页 40 | next('/login') 41 | NProgress.done() // 结束Progress 42 | } 43 | } 44 | }) 45 | 46 | router.afterEach(() => { 47 | NProgress.done() // 结束Progress 48 | }) 49 | -------------------------------------------------------------------------------- /admin/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(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/controller/AccountRoleController.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.controller; 2 | 3 | import com.zoctan.api.core.response.Result; 4 | import com.zoctan.api.core.response.ResultGenerator; 5 | import com.zoctan.api.entity.AccountRole; 6 | import com.zoctan.api.service.AccountRoleService; 7 | import org.springframework.security.access.prepost.PreAuthorize; 8 | import org.springframework.web.bind.annotation.PutMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | import java.security.Principal; 15 | 16 | /** 17 | * @author Zoctan 18 | * @date 2018/06/09 19 | */ 20 | @RestController 21 | @RequestMapping("/account/role") 22 | public class AccountRoleController { 23 | @Resource private AccountRoleService accountRoleService; 24 | 25 | @PreAuthorize("hasAuthority('role:update')") 26 | @PutMapping 27 | public Result updateAccountRole( 28 | @RequestBody final AccountRole accountRole, final Principal principal) { 29 | final AccountRole dbAccountRole = 30 | this.accountRoleService.getBy("accountId", accountRole.getAccountId()); 31 | this.accountRoleService.updateRoleIdByAccountId(accountRole); 32 | return ResultGenerator.genOkResult(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/constant/ProjectConstant.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.constant; 2 | 3 | /** 4 | * 项目常量 5 | * 6 | * @author Zoctan 7 | * @date 2018/05/27 8 | */ 9 | public final class ProjectConstant { 10 | /** 开发环境 */ 11 | public static final String SPRING_PROFILE_DEVELOPMENT = "dev"; 12 | 13 | /** 生产环境 */ 14 | public static final String SPRING_PROFILE_PRODUCTION = "prod"; 15 | 16 | /** 测试环境 */ 17 | public static final String SPRING_PROFILE_TEST = "test"; 18 | 19 | /** 项目基础包名称 */ 20 | public static final String BASE_PACKAGE = "com.zoctan.api"; 21 | 22 | /** Entity 所在包 */ 23 | public static final String ENTITY_PACKAGE = BASE_PACKAGE + ".entity"; 24 | 25 | /** Mapper 所在包 */ 26 | public static final String MAPPER_PACKAGE = BASE_PACKAGE + ".mapper"; 27 | 28 | /** Filter 所在包 */ 29 | public static final String FILTER_PACKAGE = BASE_PACKAGE + ".filter"; 30 | 31 | /** Service 所在包 */ 32 | public static final String SERVICE_PACKAGE = BASE_PACKAGE + ".service"; 33 | 34 | /** ServiceImpl 所在包 */ 35 | public static final String SERVICE_IMPL_PACKAGE = SERVICE_PACKAGE + ".impl"; 36 | 37 | /** Controller 所在包 */ 38 | public static final String CONTROLLER_PACKAGE = BASE_PACKAGE + ".controller"; 39 | 40 | /** Mapper 插件基础接口的完全限定名 */ 41 | public static final String MAPPER_INTERFACE_REFERENCE = BASE_PACKAGE + ".core.mapper.MyMapper"; 42 | } 43 | -------------------------------------------------------------------------------- /api/src/test/java/com/tsbtv/report/WithCustomSecurityContextFactory.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api; 2 | 3 | import com.zoctan.api.service.impl.AccountDetailsServiceImpl; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.context.SecurityContext; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.test.context.support.WithSecurityContextFactory; 10 | 11 | import javax.annotation.Resource; 12 | 13 | /** 14 | * 设置用户登陆时的 SecurityContext 15 | * 16 | * @author Zoctan 17 | * @date 2018/11/29 18 | */ 19 | public class WithCustomSecurityContextFactory 20 | implements WithSecurityContextFactory { 21 | @Resource private AccountDetailsServiceImpl accountDetailsService; 22 | 23 | @Override 24 | public SecurityContext createSecurityContext(final WithCustomUser customUser) { 25 | final SecurityContext context = SecurityContextHolder.createEmptyContext(); 26 | final UserDetails userDetails = 27 | this.accountDetailsService.loadUserByUsername(customUser.name()); 28 | final Authentication auth = 29 | new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 30 | context.setAuthentication(auth); 31 | return context; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/util/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.serializer.SimplePropertyPreFilter; 5 | 6 | import java.util.Arrays; 7 | 8 | /** 9 | * Json工具 10 | * 11 | * @author Zoctan 12 | * @date 2018/07/11 13 | */ 14 | public class JsonUtils { 15 | private JsonUtils() {} 16 | 17 | /** 18 | * 保留某些字段 19 | * 20 | * @param target 目标对象 21 | * @param fields 字段 22 | * @return 保留字段后的对象 23 | */ 24 | public static T keepFields(final Object target, final Class clz, final String... fields) { 25 | final SimplePropertyPreFilter filter = new SimplePropertyPreFilter(); 26 | filter.getIncludes().addAll(Arrays.asList(fields)); 27 | return done(target, clz, filter); 28 | } 29 | 30 | /** 31 | * 去除某些字段 32 | * 33 | * @param target 目标对象 34 | * @param fields 字段 35 | * @return 去除字段后的对象 36 | */ 37 | public static T deleteFields( 38 | final Object target, final Class clz, final String... fields) { 39 | final SimplePropertyPreFilter filter = new SimplePropertyPreFilter(); 40 | filter.getExcludes().addAll(Arrays.asList(fields)); 41 | return done(target, clz, filter); 42 | } 43 | 44 | private static T done( 45 | final Object target, final Class clz, final SimplePropertyPreFilter filter) { 46 | final String jsonString = JSON.toJSONString(target, filter); 47 | return JSON.parseObject(jsonString, clz); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/Levelbar.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 42 | 43 | 55 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/filter/MyAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.filter; 2 | 3 | import com.zoctan.api.core.response.ResultCode; 4 | import com.zoctan.api.core.response.ResultGenerator; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.security.core.AuthenticationException; 7 | import org.springframework.security.web.AuthenticationEntryPoint; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.io.Serializable; 14 | import java.nio.charset.StandardCharsets; 15 | 16 | /** 17 | * 认证入口点 因为 RESTFul 没有登录界面所以只显示未登录提示 18 | * 19 | * @author Zoctan 20 | * @date 2018/05/27 21 | */ 22 | @Component 23 | public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { 24 | @Override 25 | public void commence( 26 | final HttpServletRequest request, 27 | final HttpServletResponse response, 28 | final AuthenticationException authException) 29 | throws IOException { 30 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 31 | response.setHeader("Content-type", MediaType.APPLICATION_JSON_UTF8_VALUE); 32 | response.setCharacterEncoding(StandardCharsets.UTF_8.displayName()); 33 | response 34 | .getWriter() 35 | .println(ResultGenerator.genFailedResult(ResultCode.UNAUTHORIZED_EXCEPTION).toString()); 36 | response.getWriter().close(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/controller/PermissionController.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.controller; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import org.springframework.security.access.prepost.PreAuthorize; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import com.zoctan.api.core.response.Result; 11 | import com.zoctan.api.core.response.ResultGenerator; 12 | import com.zoctan.api.service.PermissionService; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.List; 16 | 17 | /** 18 | * @author Zoctan 19 | * @date 2018/06/09 20 | */ 21 | @RestController 22 | @RequestMapping("/permission") 23 | public class PermissionController { 24 | @Resource private PermissionService permissionService; 25 | 26 | @PreAuthorize("hasAuthority('role:list')") 27 | @GetMapping 28 | public Result listResourcePermission( 29 | @RequestParam(defaultValue = "0") final Integer page, 30 | @RequestParam(defaultValue = "0") final Integer size) { 31 | PageHelper.startPage(page, size); 32 | final List list = 33 | this.permissionService.listResourceWithHandle(); 34 | final PageInfo pageInfo = new PageInfo<>(list); 35 | return ResultGenerator.genOkResult(pageInfo); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /admin/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').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/config/ValidatorConfig.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.config; 2 | 3 | import org.hibernate.validator.HibernateValidator; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; 7 | 8 | import javax.validation.Validation; 9 | import javax.validation.Validator; 10 | import javax.validation.ValidatorFactory; 11 | 12 | /** 13 | * 参数校验 14 | * https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-constraint-violation-methods 15 | * 16 | * @author Zoctan 17 | * @date 2018/05/27 18 | */ 19 | @Configuration 20 | public class ValidatorConfig { 21 | @Bean 22 | public MethodValidationPostProcessor methodValidationPostProcessor() { 23 | final MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor(); 24 | // 设置 validator 模式为快速失败返回 25 | postProcessor.setValidator(this.validatorFailFast()); 26 | return postProcessor; 27 | // 默认是普通模式,会返回所有的验证不通过信息集合 28 | // return new MethodValidationPostProcessor(); 29 | } 30 | 31 | @Bean 32 | public Validator validatorFailFast() { 33 | final ValidatorFactory validatorFactory = 34 | Validation.byProvider(HibernateValidator.class) 35 | .configure() 36 | .addProperty("hibernate.validator.fail_fast", "true") 37 | .buildValidatorFactory(); 38 | return validatorFactory.getValidator(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | devtools: 6 | restart: 7 | # 修改代码后自动重启 8 | enabled: true 9 | # 数据源(应该全部加密) 10 | datasource: 11 | # 连接,注意各个配置,尤其是要一次性执行多条 SQL 时,要 allowMultiQueries=true 12 | url: jdbc:mysql://localhost:3306/admin_dev?useUnicode=true&useSSL=false&allowMultiQueries=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=UTC 13 | # 用户名 root 14 | username: MyEnc({btTSNb3PH7saU2yQ7FeCsQ==}) 15 | # 密码 root 16 | password: MyEnc({VuLn6kkmZXt1402C9w9xUA==}) 17 | # 驱动类 18 | driver-class-name: com.mysql.cj.jdbc.Driver 19 | cache: 20 | # 缓存类型 21 | type: redis 22 | redis: 23 | # key 前缀 24 | key-prefix: admin_dev 25 | # 过期时间 26 | time-to-live: 60s 27 | redis: 28 | # 数据库索引(默认为0) 29 | database: 0 30 | # 服务器地址 31 | host: 127.0.0.1 32 | # 服务器连接端口 33 | port: 6379 34 | # 服务器连接密码 root 35 | # password: MyEnc({eCOS8Sk9b/kWt2FK0QFA9g==}) 36 | jedis.pool: 37 | # 连接池最大连接数(使用负值表示没有限制) 38 | max-active: 8 39 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 40 | max-wait: -1ms 41 | # 连接池中的最大空闲连接 42 | max-idle: 8 43 | # 连接池中的最小空闲连接 44 | min-idle: 0 45 | 46 | logging: 47 | # 日志级别 48 | level.com.zoctan.api: debug 49 | 50 | # Json web token 51 | jwt: 52 | # 管理后台过期时间 53 | admin-expire-time: 1d 54 | # 小程序前台过期时间 55 | wechat-expire-time: 30d 56 | # claim 权限 key 57 | claim-key-auth: auth 58 | # 请求头或请求参数的 key 59 | header: Authorization 60 | # token 类型 61 | token-type: Bearer 62 | 63 | -------------------------------------------------------------------------------- /api/src/main/resources/mapper/RoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | UPDATE role 32 | SET update_time = NOW() 33 | WHERE id = #{id} 34 | 35 | 36 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/impl/AccountDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service.impl; 2 | 3 | import com.zoctan.api.dto.AccountWithRolePermission; 4 | import com.zoctan.api.service.AccountService; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * @author Zoctan 17 | * @date 2018/06/09 18 | */ 19 | @Service 20 | @Transactional(rollbackFor = Exception.class) 21 | public class AccountDetailsServiceImpl implements UserDetailsService { 22 | @Resource private AccountService accountService; 23 | 24 | @Override 25 | public UserDetails loadUserByUsername(final String name) { 26 | final AccountWithRolePermission accountWithRolePermission = 27 | this.accountService.findDetailByName(name); 28 | // 权限 29 | final List authorities = 30 | accountWithRolePermission.getPermissionCodeList().stream() 31 | .map(SimpleGrantedAuthority::new) 32 | .collect(Collectors.toList()); 33 | // 角色 34 | authorities.add(new SimpleGrantedAuthority(accountWithRolePermission.getRoleName())); 35 | return new org.springframework.security.core.userdetails.User( 36 | accountWithRolePermission.getName(), "", authorities); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /api/src/main/resources/mapper/PermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 34 | -------------------------------------------------------------------------------- /admin/src/api/account.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function search(searchForm) { 4 | return request({ 5 | url: '/account/search', 6 | method: 'post', 7 | data: searchForm 8 | }) 9 | } 10 | 11 | export function list(params) { 12 | return request({ 13 | url: '/account', 14 | method: 'get', 15 | params 16 | }) 17 | } 18 | 19 | export function validatePassword(accountForm) { 20 | return request({ 21 | url: '/account/password', 22 | method: 'post', 23 | data: accountForm 24 | }) 25 | } 26 | 27 | export function update(accountForm) { 28 | return request({ 29 | url: '/account/detail', 30 | method: 'put', 31 | data: accountForm 32 | }) 33 | } 34 | 35 | export function updateAccount(accountForm) { 36 | return request({ 37 | url: '/account/' + accountForm.Id, 38 | method: 'put', 39 | data: accountForm 40 | }) 41 | } 42 | 43 | export function remove(accountId) { 44 | return request({ 45 | url: '/account/' + accountId, 46 | method: 'delete' 47 | }) 48 | } 49 | 50 | export function register(accountForm) { 51 | return request({ 52 | url: '/account', 53 | method: 'post', 54 | data: accountForm 55 | }) 56 | } 57 | 58 | export function login(accountForm) { 59 | return request({ 60 | url: '/account/token', 61 | method: 'post', 62 | data: accountForm 63 | }) 64 | } 65 | 66 | export function detail() { 67 | return request({ 68 | url: '/account/detail', 69 | method: 'get' 70 | }) 71 | } 72 | 73 | export function logout() { 74 | return request({ 75 | url: '/account/token', 76 | method: 'delete' 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/Application.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api; 2 | 3 | import com.zoctan.api.core.constant.ProjectConstant; 4 | import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.builder.SpringApplicationBuilder; 8 | import org.springframework.boot.web.servlet.ServletComponentScan; 9 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 10 | import org.springframework.cache.annotation.EnableCaching; 11 | import org.springframework.transaction.annotation.EnableTransactionManagement; 12 | import tk.mybatis.spring.annotation.MapperScan; 13 | 14 | import javax.annotation.PostConstruct; 15 | import java.util.TimeZone; 16 | 17 | /** 18 | * 主程序 19 | * 20 | * @author Zoctan 21 | * @date 2018/05/27 22 | */ 23 | @EnableCaching 24 | @SpringBootApplication 25 | @EnableEncryptableProperties 26 | @EnableTransactionManagement 27 | @MapperScan(basePackages = ProjectConstant.MAPPER_PACKAGE) 28 | @ServletComponentScan(basePackages = ProjectConstant.FILTER_PACKAGE) 29 | public class Application extends SpringBootServletInitializer { 30 | 31 | @PostConstruct 32 | void started() { 33 | TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); 34 | } 35 | 36 | public static void main(final String[] args) { 37 | SpringApplication.run(Application.class, args); 38 | } 39 | 40 | /** 容器启动配置 */ 41 | @Override 42 | protected SpringApplicationBuilder configure(final SpringApplicationBuilder builder) { 43 | return builder.sources(Application.class); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/config/Swagger2Config.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.ApiInfoBuilder; 6 | import springfox.documentation.builders.PathSelectors; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.service.Contact; 10 | import springfox.documentation.spi.DocumentationType; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 13 | 14 | import static com.zoctan.api.core.constant.ProjectConstant.CONTROLLER_PACKAGE; 15 | 16 | /** 17 | * Swagger2 在线API文档 http://springfox.github.io/springfox/docs/current/#getting-started 18 | * 19 | * @author Zoctan 20 | * @date 2018/05/27 21 | */ 22 | @Configuration 23 | @EnableSwagger2 24 | public class Swagger2Config { 25 | 26 | @Bean 27 | public Docket buildDocket() { 28 | return new Docket(DocumentationType.SWAGGER_2) 29 | .apiInfo(this.buildApiInfo()) 30 | .select() 31 | // 扫描 controller 包 32 | .apis(RequestHandlerSelectors.basePackage(CONTROLLER_PACKAGE)) 33 | .paths(PathSelectors.any()) 34 | .build(); 35 | } 36 | 37 | private ApiInfo buildApiInfo() { 38 | final Contact contact = new Contact("Zoctan", "https://github.com/Zoctan", "752481828@qq.com"); 39 | 40 | return new ApiInfoBuilder() 41 | .title("APIs doc") 42 | .description("RESTFul APIs") 43 | .contact(contact) 44 | .version("1.0") 45 | .build(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /admin/test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/AccountService.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service; 2 | 3 | import com.zoctan.api.core.service.Service; 4 | import com.zoctan.api.dto.AccountDto; 5 | import com.zoctan.api.dto.AccountWithRole; 6 | import com.zoctan.api.dto.AccountWithRolePermission; 7 | import com.zoctan.api.entity.Account; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * @author Zoctan 15 | * @date 2018/06/09 16 | */ 17 | public interface AccountService extends Service { 18 | /** 19 | * 保存用户 20 | * 21 | * @param accountDto 用户 22 | */ 23 | void save(AccountDto accountDto); 24 | 25 | /** 26 | * 获取所有用户以及对应角色 27 | * 28 | * @return 用户列表 29 | */ 30 | List listAllWithRole(); 31 | 32 | /** 33 | * 按条件查询用户 34 | * 35 | * @param params 参数 36 | * @return 用户列表 37 | */ 38 | List findWithRoleBy(final Map params); 39 | 40 | /** 41 | * 按条件查询用户信息 42 | * 43 | * @param column 列名 44 | * @param params 参数 45 | * @return 用户 46 | */ 47 | AccountWithRolePermission findDetailBy(String column, Object params); 48 | 49 | /** 50 | * 按用户名查询用户信息 51 | * 52 | * @param name 用户名 53 | * @return 用户 54 | * @throws UsernameNotFoundException 用户名找不到 55 | */ 56 | AccountWithRolePermission findDetailByName(String name) throws UsernameNotFoundException; 57 | 58 | /** 59 | * 按用户名更新最后一次登录时间 60 | * 61 | * @param name 用户名 62 | */ 63 | void updateLoginTimeByName(String name); 64 | 65 | /** 66 | * 验证用户密码 67 | * 68 | * @param rawPassword 原密码 69 | * @param encodedPassword 加密后的密码 70 | * @return boolean 71 | */ 72 | boolean verifyPassword(String rawPassword, String encodedPassword); 73 | } 74 | -------------------------------------------------------------------------------- /admin/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 47 | 48 | 58 | -------------------------------------------------------------------------------- /admin/src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | 31 | 82 | -------------------------------------------------------------------------------- /admin/src/icons/svg/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/config/JasyptConfig.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.config; 2 | 3 | import com.zoctan.api.core.rsa.RsaUtils; 4 | import org.jasypt.encryption.StringEncryptor; 5 | import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; 6 | import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.util.Base64Utils; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | * Jasypt 配置 16 | * 17 | * @author Zoctan 18 | * @date 2018/07/21 19 | */ 20 | @Configuration 21 | public class JasyptConfig { 22 | @Value("${jasypt.encryptor.password}") 23 | private String passwordEncryptedByBase64AndRSA; 24 | 25 | @Resource private RsaUtils rsaUtils; 26 | 27 | @Bean 28 | public StringEncryptor myStringEncryptor() throws Exception { 29 | // Base64 + RSA 加密的密码 30 | final byte[] passwordEncryptedByRSA = 31 | Base64Utils.decodeFromString(this.passwordEncryptedByBase64AndRSA); 32 | final String password = new String(this.rsaUtils.decrypt(passwordEncryptedByRSA)); 33 | // 配置 34 | final SimpleStringPBEConfig config = 35 | new SimpleStringPBEConfig() { 36 | { 37 | this.setPassword(password); 38 | // 加密算法 39 | this.setAlgorithm("PBEWithMD5AndDES"); 40 | this.setKeyObtentionIterations("1000"); 41 | this.setPoolSize("1"); 42 | this.setProviderName("SunJCE"); 43 | this.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); 44 | this.setStringOutputType("base64"); 45 | } 46 | }; 47 | final PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); 48 | encryptor.setConfig(config); 49 | return encryptor; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /admin/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/token' 5 | 6 | // 创建axios实例 7 | // https://www.kancloud.cn/yunye/axios/234845 8 | const service = axios.create({ 9 | baseURL: process.env.BASE_API, // api的base_url 10 | timeout: 5000, // 请求超时时间 11 | // 所有请求都以Json形式传送 12 | // 会有预检请求,服务端需要正常通过OPTIONS请求 13 | // http://www.ruanyifeng.com/blog/2016/04/cors 14 | headers: { 15 | 'Content-type': 'application/json;charset=UTF-8' 16 | } 17 | }) 18 | 19 | // request拦截器 20 | service.interceptors.request.use(config => { 21 | if (store.getters.token) { 22 | // 让每个请求携带自定义token 请根据实际情况自行修改 23 | config.headers['Authorization'] = getToken() 24 | } 25 | return config 26 | }, error => { 27 | // Do something with request error 28 | console.debug(error) // for debug 29 | Promise.reject(error) 30 | }) 31 | 32 | // response拦截器 33 | service.interceptors.response.use( 34 | response => { 35 | if (response.data.code === 200) { 36 | return response.data 37 | } else { 38 | Message({ 39 | message: response.data.message, 40 | type: 'error', 41 | duration: 5 * 1000 42 | }) 43 | return Promise.reject('error') 44 | } 45 | }, 46 | error => { 47 | // 4002:需要认证 48 | if (error.response.data.code === 4002) { 49 | MessageBox.confirm('需要登录!', '警告', { 50 | confirmButtonText: '登录', 51 | cancelButtonText: '取消', 52 | type: 'warning' 53 | }).then(() => { 54 | store.dispatch('FedLogout').then(() => { 55 | location.reload()// 为了重新实例化vue-router对象 避免bug 56 | }) 57 | }) 58 | } else { 59 | Message({ 60 | message: error.response.data.message, 61 | type: 'error', 62 | duration: 5 * 1000 63 | }) 64 | } 65 | return Promise.reject(error) 66 | } 67 | ) 68 | 69 | export default service 70 | -------------------------------------------------------------------------------- /admin/src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/filter/RequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.filter; 2 | 3 | import javax.servlet.ReadListener; 4 | import javax.servlet.ServletInputStream; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletRequestWrapper; 7 | import java.io.BufferedReader; 8 | import java.io.ByteArrayInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | 12 | /** 13 | * 请求装饰器,用于多次读取请求流 14 | * 15 | * @author Zoctan 16 | * @date 2018/07/13 17 | */ 18 | public class RequestWrapper extends HttpServletRequestWrapper { 19 | private final StringBuilder body; 20 | 21 | public RequestWrapper(final HttpServletRequest request) throws IOException { 22 | super(request); 23 | this.body = new StringBuilder(); 24 | final BufferedReader bufferedReader = request.getReader(); 25 | String line; 26 | while ((line = bufferedReader.readLine()) != null) { 27 | this.body.append(line); 28 | } 29 | } 30 | 31 | @Override 32 | public ServletInputStream getInputStream() { 33 | final ByteArrayInputStream byteArrayInputStream = 34 | new ByteArrayInputStream(this.body.toString().getBytes()); 35 | return new ServletInputStream() { 36 | @Override 37 | public int read() { 38 | return byteArrayInputStream.read(); 39 | } 40 | 41 | @Override 42 | public boolean isFinished() { 43 | return false; 44 | } 45 | 46 | @Override 47 | public boolean isReady() { 48 | return false; 49 | } 50 | 51 | @Override 52 | public void setReadListener(final ReadListener readListener) {} 53 | }; 54 | } 55 | 56 | @Override 57 | public BufferedReader getReader() { 58 | return new BufferedReader(new InputStreamReader(this.getInputStream())); 59 | } 60 | 61 | public String getJson() { 62 | return this.getReader() 63 | .lines() 64 | .sequential() 65 | .reduce(System.lineSeparator(), (accumulator, actual) -> accumulator + actual); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/response/ResultGenerator.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.response; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | /** 6 | * 响应结果生成工具 7 | * 8 | * @author Zoctan 9 | * @date 2018/06/09 10 | */ 11 | public class ResultGenerator { 12 | /** 13 | * 成功响应结果 14 | * 15 | * @param data 内容 16 | * @return 响应结果 17 | */ 18 | public static Result genOkResult(final T data) { 19 | return new Result().setCode(HttpStatus.OK.value()).setData(data); 20 | } 21 | 22 | /** 23 | * 成功响应结果 24 | * 25 | * @return 响应结果 26 | */ 27 | public static Result genOkResult() { 28 | return genOkResult(null); 29 | } 30 | 31 | /** 32 | * 失败响应结果 33 | * 34 | * @param code 状态码 35 | * @param message 消息 36 | * @return 响应结果 37 | */ 38 | public static Result genFailedResult(final int code, final String message) { 39 | return new Result().setCode(code).setMessage(message); 40 | } 41 | 42 | /** 43 | * 失败响应结果 44 | * 45 | * @param resultCode 状态码枚举 46 | * @param message 消息 47 | * @return 响应结果 48 | */ 49 | public static Result genFailedResult(final ResultCode resultCode, final String message) { 50 | return genFailedResult(resultCode.getValue(), message); 51 | } 52 | 53 | /** 54 | * 失败响应结果 55 | * 56 | * @param resultCode 状态码枚举 57 | * @return 响应结果 58 | */ 59 | public static Result genFailedResult(final ResultCode resultCode) { 60 | return genFailedResult(resultCode.getValue(), resultCode.getReason()); 61 | } 62 | 63 | /** 64 | * 失败响应结果 65 | * 66 | * @param message 消息 67 | * @return 响应结果 68 | */ 69 | public static Result genFailedResult(final String message) { 70 | return genFailedResult(ResultCode.SUCCEED_REQUEST_FAILED_RESULT.getValue(), message); 71 | } 72 | 73 | /** 74 | * 失败响应结果 75 | * 76 | * @return 响应结果 77 | */ 78 | public static Result genFailedResult() { 79 | return genFailedResult(ResultCode.SUCCEED_REQUEST_FAILED_RESULT); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /api/src/test/resources/generator/template/controller-restful.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.controller; 2 | 3 | import ${basePackage}.core.response.Result; 4 | import ${basePackage}.core.response.ResultGenerator; 5 | import ${basePackage}.entity.${modelNameUpperCamel}; 6 | import ${basePackage}.service.${modelNameUpperCamel}Service; 7 | import com.github.pagehelper.PageHelper; 8 | import com.github.pagehelper.PageInfo; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.List; 13 | 14 | /** 15 | * @author ${author} 16 | * @date ${date} 17 | */ 18 | @RestController 19 | @RequestMapping("${baseRequestMapping}") 20 | public class ${modelNameUpperCamel}Controller { 21 | @Resource 22 | private ${modelNameUpperCamel}Service ${modelNameLowerCamel}Service; 23 | 24 | @PostMapping 25 | public Result add(@RequestBody ${modelNameUpperCamel} ${modelNameLowerCamel}) { 26 | ${modelNameLowerCamel}Service.save(${modelNameLowerCamel}); 27 | return ResultGenerator.genOkResult(); 28 | } 29 | 30 | @DeleteMapping("/{id}") 31 | public Result delete(@PathVariable Long id) { 32 | ${modelNameLowerCamel}Service.deleteById(id); 33 | return ResultGenerator.genOkResult(); 34 | } 35 | 36 | @PatchMapping 37 | public Result update(@RequestBody ${modelNameUpperCamel} ${modelNameLowerCamel}) { 38 | ${modelNameLowerCamel}Service.update(${modelNameLowerCamel}); 39 | return ResultGenerator.genOkResult(); 40 | } 41 | 42 | @GetMapping("/{id}") 43 | public Result detail(@PathVariable Long id) { 44 | ${modelNameUpperCamel} ${modelNameLowerCamel} = ${modelNameLowerCamel}Service.getById(id); 45 | return ResultGenerator.genOkResult(${modelNameLowerCamel}); 46 | } 47 | 48 | @GetMapping 49 | public Result list(@RequestParam(defaultValue = "0") Integer page, 50 | @RequestParam(defaultValue = "0") Integer size) { 51 | PageHelper.startPage(page, size); 52 | List<${modelNameUpperCamel}> list = ${modelNameLowerCamel}Service.listAll(); 53 | PageInfo<${modelNameUpperCamel}> pageInfo = PageInfo.of(list); 54 | return ResultGenerator.genOkResult(pageInfo); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /admin/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 46 | 47 | 62 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 47 | 48 | 79 | -------------------------------------------------------------------------------- /admin/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unix时间戳转换成日期格式 unix2CurrentTime("1497232433000") 3 | * @param unixTime Unix时间戳 4 | * @return string yyyy-MM-dd HH:mm:ss 5 | */ 6 | export function unix2CurrentTime(unixTime) { 7 | const date = new Date(parseInt(unixTime)) 8 | const y = date.getFullYear() 9 | let m = date.getMonth() + 1 10 | m = m < 10 ? ('0' + m) : m 11 | let d = date.getDate() 12 | d = d < 10 ? ('0' + d) : d 13 | let h = date.getHours() 14 | h = h < 10 ? ('0' + h) : h 15 | let minute = date.getMinutes() 16 | let second = date.getSeconds() 17 | minute = minute < 10 ? ('0' + minute) : minute 18 | second = second < 10 ? ('0' + second) : second 19 | return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second 20 | } 21 | 22 | /** 23 | * 两个Unix时间戳差值 24 | * @param unixTimeStart Unix时间戳1 25 | * @param unixTimeEnd Unix时间戳2 26 | * @return string xx 小时 | xx 天 27 | */ 28 | export function unixDifference(unixTimeStart, unixTimeEnd) { 29 | const difference = (unixTimeEnd - unixTimeStart) / 1000 30 | if (difference >= 86400) { 31 | return difference / 86400 + '天' 32 | } else if (difference >= 3600) { 33 | return difference / 3600 + '小时' 34 | } else if (difference >= 60) { 35 | return difference / 60 + '分钟' 36 | } else { 37 | return difference + '秒' 38 | } 39 | } 40 | 41 | /** 42 | * 当前Unix时间戳差值 43 | * @param unixTimeEnd Unix时间戳 44 | * @return string | null xx天xx小时xx分钟xx秒 45 | */ 46 | export function nowDifference(unixTimeEnd) { 47 | const unixTimeStart = new Date().getTime() 48 | const difference = (unixTimeEnd - unixTimeStart) / 1000 49 | if (difference > 0) { 50 | let day = Math.floor(difference / (60 * 60 * 24)) 51 | let hour = Math.floor(difference / (60 * 60)) - (day * 24) 52 | let minute = Math.floor(difference / 60) - (day * 24 * 60) - (hour * 60) 53 | let second = Math.floor(difference) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60) 54 | if (day <= 9) day = '0' + day 55 | if (hour <= 9) hour = '0' + hour 56 | if (minute <= 9) minute = '0' + minute 57 | if (second <= 9) second = '0' + second 58 | return day + '天' + hour + '小时' + minute + '分钟' + second + '秒' 59 | } else { 60 | return null 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /api/src/test/resources/generator/template/controller.ftl: -------------------------------------------------------------------------------- 1 | package ${basePackage}.controller; 2 | 3 | import ${basePackage}.core.response.Result; 4 | import ${basePackage}.core.response.ResultGenerator; 5 | import ${basePackage}.entity.${modelNameUpperCamel}; 6 | import ${basePackage}.service.${modelNameUpperCamel}Service; 7 | import com.github.pagehelper.PageHelper; 8 | import com.github.pagehelper.PageInfo; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.List; 16 | 17 | /** 18 | * @author ${author} 19 | * @date ${date} 20 | */ 21 | @RestController 22 | @RequestMapping("${baseRequestMapping}") 23 | public class ${modelNameUpperCamel}Controller { 24 | @Resource 25 | private ${modelNameUpperCamel}Service ${modelNameLowerCamel}Service; 26 | 27 | @PostMapping("/add") 28 | public Result add(${modelNameUpperCamel} ${modelNameLowerCamel}) { 29 | ${modelNameLowerCamel}Service.save(${modelNameLowerCamel}); 30 | return ResultGenerator.genOkResult(); 31 | } 32 | 33 | @PostMapping("/delete") 34 | public Result delete(@RequestParam Long id) { 35 | ${modelNameLowerCamel}Service.deleteById(id); 36 | return ResultGenerator.genOkResult(); 37 | } 38 | 39 | @PostMapping("/update") 40 | public Result update(${modelNameUpperCamel} ${modelNameLowerCamel}) { 41 | ${modelNameLowerCamel}Service.update(${modelNameLowerCamel}); 42 | return ResultGenerator.genOkResult(); 43 | } 44 | 45 | @PostMapping("/detail") 46 | public Result detail(@RequestParam Long id) { 47 | ${modelNameUpperCamel} ${modelNameLowerCamel} = ${modelNameLowerCamel}Service.getById(id); 48 | return ResultGenerator.genOkResult(${modelNameLowerCamel}); 49 | } 50 | 51 | @PostMapping("/list") 52 | public Result list(@RequestParam(defaultValue = "0") Integer page, 53 | @RequestParam(defaultValue = "0") Integer size) { 54 | PageHelper.startPage(page, size); 55 | List<${modelNameUpperCamel}> list = ${modelNameLowerCamel}Service.listAll(); 56 | PageInfo<${modelNameUpperCamel}> pageInfo = PageInfo.of(list); 57 | return ResultGenerator.genOkResult(pageInfo); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /admin/src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '@/router/index' 2 | 3 | /** 4 | * 通过meta.auth判断是否与当前用户权限匹配 5 | * @param permissionCodeList 6 | * @param route 7 | */ 8 | function hasPermission(permissionCodeList, route) { 9 | if (route.meta && route.meta.permission) { 10 | return permissionCodeList.some(permission => route.meta.permission.indexOf(permission) >= 0) 11 | } else { 12 | return true 13 | } 14 | } 15 | 16 | /** 17 | * 递归过滤异步路由表,返回符合用户角色权限的路由表 18 | * @param asyncRouterMap 19 | * @param permissionCodeList 20 | */ 21 | function filterAsyncRouter(asyncRouterMap, permissionCodeList) { 22 | return asyncRouterMap.filter(route => { 23 | // filter,js语法里数组的过滤筛选方法 24 | if (hasPermission(permissionCodeList, route)) { 25 | if (route.children && route.children.length) { 26 | // 如果这个路由下面还有下一级的话,就递归调用 27 | route.children = filterAsyncRouter(route.children, permissionCodeList) 28 | // 如果过滤一圈后,没有子元素了,这个父级菜单就也不显示了 29 | // return (route.children && route.children.length) 30 | } 31 | return true 32 | } 33 | return false 34 | }) 35 | } 36 | 37 | const permission = { 38 | state: { 39 | routers: constantRouterMap, // 本用户所有的路由,包括了固定的路由和下面的addRouters 40 | addRouters: [] // 本用户的角色赋予的新增的动态路由 41 | }, 42 | mutations: { 43 | SET_ROUTERS: (state, routers) => { 44 | state.addRouters = routers 45 | state.routers = constantRouterMap.concat(routers) // 将固定路由和新增路由进行合并, 成为本用户最终的全部路由信息 46 | } 47 | }, 48 | actions: { 49 | GenerateRoutes({ commit }, account) { 50 | return new Promise(resolve => { 51 | const role = account.roleName 52 | const permissionCodeList = account.permissionCodeList 53 | // 声明 该角色可用的路由 54 | let accessedRouters 55 | if (role === '超级管理员') { 56 | // 如果角色里包含'超级管理员',那么所有的路由都可以用 57 | // 其实管理员也拥有全部菜单,这里主要是利用角色判断,节省加载时间 58 | accessedRouters = asyncRouterMap 59 | } else { 60 | // 否则需要通过以下方法来筛选出本角色可用的路由 61 | accessedRouters = filterAsyncRouter(asyncRouterMap, permissionCodeList) 62 | } 63 | // 执行设置路由的方法 64 | commit('SET_ROUTERS', accessedRouters) 65 | resolve() 66 | }) 67 | } 68 | } 69 | } 70 | 71 | export default permission 72 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/util/AesUtils.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.util; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 5 | 6 | import javax.crypto.BadPaddingException; 7 | import javax.crypto.Cipher; 8 | import javax.crypto.IllegalBlockSizeException; 9 | import javax.crypto.NoSuchPaddingException; 10 | import javax.crypto.spec.IvParameterSpec; 11 | import javax.crypto.spec.SecretKeySpec; 12 | import java.nio.charset.Charset; 13 | import java.security.*; 14 | import java.security.spec.InvalidParameterSpecException; 15 | 16 | /** 17 | * AES加解密工具 18 | * 19 | * @author Zoctan 20 | * @date 2018/11/29 21 | */ 22 | public class AesUtils { 23 | static { 24 | // http://www.bouncycastle.org/ 25 | Security.addProvider(new BouncyCastleProvider()); 26 | } 27 | 28 | /** 29 | * AES解密 30 | * 31 | * @param data 密文,被加密的数据 32 | * @param key 秘钥 33 | * @param iv 偏移量 34 | * @param encodingFormat 解密后的结果需要进行的编码 35 | */ 36 | public static String decrypt( 37 | final String data, final String key, final String iv, final Charset encodingFormat) { 38 | // 被加密的数据 39 | final byte[] dataByte = Base64.decodeBase64(data); 40 | // 加密秘钥 41 | final byte[] keyByte = Base64.decodeBase64(key); 42 | // 偏移量 43 | final byte[] ivByte = Base64.decodeBase64(iv); 44 | try { 45 | final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC"); 46 | final SecretKeySpec spec = new SecretKeySpec(keyByte, "AES"); 47 | final AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES"); 48 | parameters.init(new IvParameterSpec(ivByte)); 49 | cipher.init(Cipher.DECRYPT_MODE, spec, parameters); 50 | final byte[] resultByte = cipher.doFinal(dataByte); 51 | if (null != resultByte && resultByte.length > 0) { 52 | return new String(resultByte, encodingFormat); 53 | } 54 | return null; 55 | } catch (final NoSuchAlgorithmException 56 | | NoSuchPaddingException 57 | | InvalidParameterSpecException 58 | | InvalidKeyException 59 | | InvalidAlgorithmParameterException 60 | | IllegalBlockSizeException 61 | | BadPaddingException 62 | | NoSuchProviderException e) { 63 | e.printStackTrace(); 64 | } 65 | return null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/service/impl/RoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.service.impl; 2 | 3 | import com.zoctan.api.core.service.AbstractService; 4 | import com.zoctan.api.dto.RoleWithPermission; 5 | import com.zoctan.api.dto.RoleWithResource; 6 | import com.zoctan.api.entity.Role; 7 | import com.zoctan.api.entity.RolePermission; 8 | import com.zoctan.api.mapper.PermissionMapper; 9 | import com.zoctan.api.mapper.RoleMapper; 10 | import com.zoctan.api.mapper.RolePermissionMapper; 11 | import com.zoctan.api.service.RoleService; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | import tk.mybatis.mapper.entity.Condition; 15 | 16 | import javax.annotation.Resource; 17 | import java.util.List; 18 | 19 | /** 20 | * @author Zoctan 21 | * @date 2018/06/09 22 | */ 23 | @Service 24 | @Transactional(rollbackFor = Exception.class) 25 | public class RoleServiceImpl extends AbstractService implements RoleService { 26 | @Resource private RoleMapper roleMapper; 27 | @Resource private PermissionMapper permissionMapper; 28 | @Resource private RolePermissionMapper rolePermissionMapper; 29 | 30 | @Override 31 | public List listRoleWithPermission() { 32 | // 由于mybatis在嵌套查询时和pagehelper有冲突 33 | // 暂时用for循环代替 34 | final List roles = this.roleMapper.listRoles(); 35 | roles.forEach( 36 | role -> { 37 | final List resources = 38 | this.permissionMapper.listRoleWithResourceByRoleId(role.getId()); 39 | role.setResourceList(resources); 40 | }); 41 | return roles; 42 | } 43 | 44 | @Override 45 | public void save(final RoleWithPermission role) { 46 | this.roleMapper.insert(role); 47 | this.rolePermissionMapper.saveRolePermission(role.getId(), role.getPermissionIdList()); 48 | } 49 | 50 | @Override 51 | public void update(final RoleWithPermission role) { 52 | // 删掉所有权限,再添加回去 53 | final Condition condition = new Condition(RolePermission.class); 54 | condition.createCriteria().andCondition("role_id = ", role.getId()); 55 | this.rolePermissionMapper.deleteByCondition(condition); 56 | this.rolePermissionMapper.saveRolePermission(role.getId(), role.getPermissionIdList()); 57 | this.roleMapper.updateTimeById(role.getId()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /api/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | # 激活的配置 4 | active: dev 5 | # 终端彩色输出信息 6 | output.ansi.enabled: ALWAYS 7 | resources: 8 | # 不映射工程中的静态资源文件比如:html、css 9 | # 如果某些情况需要映射 10 | # 比如 swagger2,可以在 addResourceHandlers 和 addViewControllers 中特别添加,参考 WebMvcConfig 11 | add-mappings: false 12 | mvc: 13 | # 当出现 404 错误时,直接抛出异常(默认是显示一个错误页面) 14 | throw-exception-if-no-handler-found: true 15 | freemarker: 16 | # 关闭模版检查 17 | checkTemplateLocation: false 18 | 19 | rsa: 20 | # 私钥位置 21 | # private-key-path: rsa/private-key.pem 22 | # 公钥位置 23 | # public-key-path: rsa/public-key.pem 24 | use-file: false 25 | private-key: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2Xjcb+LtTzoPalaHpRDvCGt10f1AOpMGhmNvJKTewhLZb8ChmcLAkCFhh9C1jqpnin2hbAf05ALtn/xLdboznwIDAQABAkEAhc3iO4kxH9UGVRQmY352xARyOqCKWz/I/PjDEpXKZTdWs1oqTM2lpYBlpnV/fJ3Sf2es6Sv6reLCLP7MZP1KGQIhAP0+wZ0c1YBVJvkkHHmhnGyaU1Bsb1alPY4A2sM97Q0lAiEA29Z7rVhw4Fgx201uhnwb0dqiuXfCZM1+fVDyTSg0XHMCIBZencGgE2fjna6yNuWzldquAx/+hBM2Q2qwvqIybScVAiEAlFhnnNHRWZIqEpJtwtJ8819V71GhG+SPNoEpAGfg7YECIHPReHdJdfBehhD1o63xH+gTZqliK4n6XvBhkcyWNYdS 26 | public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANl43G/i7U86D2pWh6UQ7whrddH9QDqTBoZjbySk3sIS2W/AoZnCwJAhYYfQtY6qZ4p9oWwH9OQC7Z/8S3W6M58CAwEAAQ== 27 | 28 | jasypt.encryptor: 29 | # 先 RSA + 后 Base64 加密的密码 30 | # 在 JasyptConfig#myStringEncryptor 中先解密后再使用 31 | password: VQoiLSHvARy1uHWnZGb8dLwy8Mx9wvanJq1oDT/0fudbF0tjs8LWYkGGPQdIkBjioms1RcQNOoYQRH8gAtphPg== 32 | # 自定义的加密器 33 | bean: myStringEncryptor 34 | # 自定义被加密值的发现器 35 | property: 36 | detector-bean: myEncryptablePropertyDetector 37 | 38 | mybatis: 39 | # 存放实体的位置 40 | type-aliases-package: com.zoctan.api.entity 41 | # 存放 mapper 映射文件的位置 42 | mapper-locations: classpath:mapper/*.xml 43 | 44 | mapper: 45 | # 多个接口时逗号隔开 46 | mappers: com.zoctan.api.core.mapper.MyMapper 47 | # insert 和 update 中,判断字符串类型 != '' 48 | not-empty: false 49 | # 取回主键的方式 50 | identity: MYSQL 51 | 52 | # 分页插件 53 | # https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md 54 | pagehelper: 55 | # pageSize=0 时查出所有结果,相当于没分页 56 | page-size-zero: true 57 | # 数据库方言 58 | helperDialect: mysql 59 | # 分页合理化 60 | # pageNum <= 0 时会查询第一页 61 | # pageNum > pages(超过总数时),会查询最后一页 62 | reasonable: true 63 | # 支持通过 Mapper 接口参数来传递分页参数 64 | supportMethodsArguments: true 65 | 66 | # 日志 67 | #logging: 68 | # # 以文件方式记录日志 69 | # file: admin.log 70 | # # 设置目录 71 | # path: /var/log -------------------------------------------------------------------------------- /admin/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | // 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 4 | 5 | /* layout */ 6 | import Layout from '../views/layout/Layout' 7 | 8 | const _import = require('./_import_' + process.env.NODE_ENV) 9 | 10 | Vue.use(Router) 11 | 12 | /** 13 | * icon : the icon show in the sidebar 14 | * hidden : if `hidden:true` will not show in the sidebar 15 | * redirect : if `redirect:noRedirect` will not redirect in the levelBar 16 | * noDropDown : if `noDropDown:true` will not has submenu in the sidebar 17 | * meta : `{ permission: ['a:xx'] }` will control the page permission 18 | **/ 19 | export const constantRouterMap = [ 20 | { path: '/login', component: _import('login/index'), hidden: true }, 21 | { path: '/404', component: _import('errorPage/404'), hidden: true }, 22 | { path: '/401', component: _import('errorPage/401'), hidden: true }, 23 | { 24 | path: '', 25 | component: Layout, 26 | redirect: 'dashboard', 27 | icon: 'dashboard', 28 | noDropDown: true, 29 | children: [{ 30 | path: 'dashboard', 31 | name: '控制台', 32 | component: _import('dashboard/index'), 33 | meta: { title: 'dashboard', noCache: true } 34 | }] 35 | } 36 | ] 37 | 38 | export default new Router({ 39 | // mode: 'history', //后端支持可开 40 | scrollBehavior: () => ({ y: 0 }), 41 | routes: constantRouterMap 42 | }) 43 | 44 | export const asyncRouterMap = [ 45 | { 46 | path: '/account', 47 | component: Layout, 48 | redirect: '/account/list', 49 | icon: 'name', 50 | noDropDown: true, 51 | children: [{ 52 | path: 'list', 53 | name: '账户管理', 54 | component: _import('account/list'), 55 | meta: { permission: ['account:list'] } 56 | }] 57 | }, 58 | 59 | { 60 | path: '/account', 61 | component: Layout, 62 | redirect: '/account/detail', 63 | hidden: true, 64 | children: [{ 65 | path: 'detail', 66 | name: '账户中心', 67 | component: _import('account/detail') 68 | }] 69 | }, 70 | 71 | { 72 | path: '/role', 73 | component: Layout, 74 | redirect: '/role/list', 75 | icon: 'role', 76 | noDropDown: true, 77 | children: [{ 78 | path: 'list', 79 | name: '角色管理', 80 | component: _import('role/list'), 81 | meta: { permission: ['role:list'] } 82 | }] 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /admin/src/views/errorPage/401.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 57 | 58 | 94 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Vue Admin 2 | 3 | 提供一套前后端分离的后台权限管理模版。 4 | 5 | ![stars](https://img.shields.io/github/stars/Zoctan/spring-boot-vue-admin.svg?style=flat-square&label=Stars) 6 | ![license](https://img.shields.io/github/license/Zoctan/spring-boot-vue-admin.svg?style=flat-square) 7 | 8 | 简体中文 | [English](./README.md) 9 | 10 | 前端思路参考[《手摸手,带你用vue撸后台 系列二(登录权限篇)》](https://juejin.im/post/591aa14f570c35006961acac),模板来自 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),其他功能可以根据该项目进行拓展。 11 | 12 | 后端思路参考[《Role-Based Access Control 新解》](http://globeeip.iteye.com/blog/1236167),模板来自 [spring-boot-api-seedling](https://github.com/Zoctan/spring-boot-api-seedling.git),设计思路请看 api 的 [README](https://github.com/Zoctan/spring-boot-vue-admin/tree/master/api)。 13 | 14 | 注:由于好几年没更新前端了,所以有漏洞以及版本落后的问题,如有需要请参考用 Vue3 写的新项目:[admin-vue3-template](https://github.com/Zoctan/admin-vue3-template)。 15 | 16 | 欢迎小伙伴 star 和 issues ~ 谢谢 :) 17 | 18 | # 预览 19 | 20 | ![权限列表](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/1.png) 21 | 22 | ![角色管理](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/2.png) 23 | 24 | ![用户管理](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/3.png) 25 | 26 | ![用户角色控制](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/4.png) 27 | 28 | # 依赖版本 29 | 30 | 前端依赖 | 版本 31 | --------|------ 32 | node | 8.16.1 33 | npm | 6.4.1 34 | 35 | 后端依赖 | 版本 36 | -----------|------ 37 | SpringBoot | 2.1.6 38 | 39 | # 快速开始 40 | 41 | ```markdown 42 | # 克隆项目 43 | git clone https://github.com/Zoctan/spring-boot-vue-admin.git 44 | 45 | # 进入项目 46 | cd spring-boot-vue-admin 47 | 48 | # 后端 49 | cd api 50 | 51 | # 导入数据库文件(记得修改数据库信息) 52 | sudo chmod a+x resetDB.sh && ./resetDB.sh 53 | 54 | # 启动后端服务... 55 | 56 | # 前端 57 | cd app 58 | 59 | # 安装依赖 60 | npm install 61 | 62 | # 启动前端服务 63 | npm run dev 64 | ``` 65 | 66 | # 问题解决 67 | 68 | ## no such file/ansi-styles/css-loader 69 | 70 | 如果出现以下错误,请先单独安装 `npm install css-loader`,再安装项目依赖 `npm install`。 71 | 72 | ```bash 73 | npm ERR! enoent ENOENT: no such file or directory, rename '/workspace/spring-boot-vue-admin/app/node_modules/.staging/css-loader-b931fe48/node_modules/ansi-styles' -> '/workspace/spring-boot-vue-admin/app/node_modules/.staging/ansi-styles-6535fafb' 74 | ``` 75 | 76 | # 更新日志 77 | 78 | 2019-10-16 回退 webpack 版本,暂时没时间修复新版问题。更新已发现的问题,完全按照后端模板 [spring-boot-api-seedling](https://github.com/Zoctan/spring-boot-api-seedling.git) 添加业务。 79 | 80 | ~~2018-06-10 由于 Redis 主要充当缓存数据库,但在该项目没起多大作用,故而移除 Redis。注意,如果需要在注销时使得 token 无效就需要搭配使用 Redis,可以自行根据后端模板进行添加。~~ 81 | -------------------------------------------------------------------------------- /admin/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 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: 9999, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 17 | autoOpenBrowser: false, 18 | errorOverlay: true, 19 | notifyOnErrors: true, 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-module-eval-source-map', 36 | 37 | // If you have problems debugging vue-files in devtools, 38 | // set this to false - it *may* help 39 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 40 | cacheBusting: true, 41 | 42 | cssSourceMap: true 43 | }, 44 | 45 | build: { 46 | // Template for index.html 47 | index: path.resolve(__dirname, '../dist/index.html'), 48 | 49 | // Paths 50 | assetsRoot: path.resolve(__dirname, '../dist'), 51 | assetsSubDirectory: 'static', 52 | assetsPublicPath: '/', 53 | 54 | /** 55 | * Source Maps 56 | */ 57 | 58 | productionSourceMap: true, 59 | // https://webpack.js.org/configuration/devtool/#production 60 | devtool: '#source-map', 61 | 62 | // Gzip off by default as many popular static hosts such as 63 | // Surge or Netlify already gzip all static assets for you. 64 | // Before setting to `true`, make sure to: 65 | // npm install --save-dev compression-webpack-plugin 66 | productionGzip: false, 67 | productionGzipExtensions: ['js', 'css'], 68 | 69 | // Run the build command with an extra argument to 70 | // View the bundle analyzer report after build finishes: 71 | // `npm run build --report` 72 | // Set to `true` or `false` to always turn it on or off 73 | bundleAnalyzerReport: process.env.npm_config_report 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /admin/src/icons/svg/role.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////// 2 | // // 3 | // :: // 4 | // ttttii;;, // 5 | // itttttii;i;, // 6 | // tttttjttii;i;;: // 7 | // tiiittjtiiii;;;; // 8 | // iiittittttiii;i;; // 9 | // iiiiittjiiii;;i;, // 10 | // iitiitttti;;;i;; // 11 | // tiitiiitjii;;;;; // 12 | // tiiitiiitji;;i;, // 13 | // :,.iiiiiiiii;;i;; // 14 | // ,iiiii;iitiiiiii;ii // 15 | // iiiiti;;;;;iiiiiiti ijtttt // 16 | // iitiiti;i;;;iiiii f jjjjtttti, // 17 | // iiiiiiiiiiiiii;;. , .fjfjjjttttii // 18 | // iiiiiiiiiiiiii;;;; i iitttttttjttttii // 19 | // :iiiiiiiii;iiiiii;;t t iitttiittttttttii // 20 | // iiiiiiiiiiiiiiiiitttitit ;ttitiitiitjjttii // 21 | // iiiiiiiiiiittiiiiiiitt. j L,tiitiiiittttiii // 22 | // ;iitiiiiiiiittiiiitt;j f L tiiitiitttti // 23 | // ,;iiiiiiii j G G iiiitttii // 24 | // f GE :iiitii // 25 | // j GL iiii // 26 | // fDG iii // 27 | // DG ti: // 28 | // DG ti // 29 | // D ; // 30 | // LG: // 31 | // Gf // 32 | // DL // 33 | // GL // 34 | // GG // 35 | // LG // 36 | // // 37 | // Great oaks from little acorns grow. // 38 | // // 39 | /////////////////////////////////////////////////////////////// -------------------------------------------------------------------------------- /api/src/test/resources/sql/dev/role.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.16 Distrib 10.1.26-MariaDB, for debian-linux-gnu (x86_64) 2 | -- 3 | -- Host: localhost Database: admin_dev 4 | -- ------------------------------------------------------ 5 | -- Server version 10.1.26-MariaDB-0+deb9u1 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE = @@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE = '+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */; 15 | /*!40101 SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; 17 | 18 | -- 19 | -- Table structure for table `role` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `role`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `role` 26 | ( 27 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '角色Id', 28 | `name` varchar(64) DEFAULT NULL COMMENT '角色名称', 29 | `create_time` datetime DEFAULT NOW() COMMENT '创建时间', 30 | `update_time` datetime DEFAULT NULL COMMENT '修改时间', 31 | PRIMARY KEY (`id`), 32 | UNIQUE KEY `name` (`name`) 33 | ) ENGINE = InnoDB 34 | AUTO_INCREMENT = 4 35 | DEFAULT CHARSET = utf8mb4 COMMENT ='角色表'; 36 | /*!40101 SET character_set_client = @saved_cs_client */; 37 | 38 | -- 39 | -- Dumping data for table `role` 40 | -- 41 | 42 | LOCK TABLES `role` WRITE; 43 | /*!40000 ALTER TABLE `role` 44 | DISABLE KEYS */; 45 | INSERT INTO `role` 46 | VALUES (1, '超级管理员', '2019-07-01 00:00:00', '2019-07-01 00:00:00'); 47 | INSERT INTO `role` 48 | VALUES (2, '普通用户', '2019-07-01 00:00:00', '2019-07-01 00:00:00'); 49 | INSERT INTO `role` 50 | VALUES (3, '测试', '2019-07-01 00:00:00', '2019-07-01 00:00:00'); 51 | /*!40000 ALTER TABLE `role` 52 | ENABLE KEYS */; 53 | UNLOCK TABLES; 54 | /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; 55 | 56 | /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; 57 | /*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */; 58 | /*!40014 SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS */; 59 | /*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */; 60 | /*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */; 61 | /*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */; 62 | /*!40111 SET SQL_NOTES = @OLD_SQL_NOTES */; 63 | 64 | -- Dump completed on 2018-02-16 20:28:17 65 | -------------------------------------------------------------------------------- /api/src/test/resources/sql/dev/account_role.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.16 Distrib 10.1.26-MariaDB, for debian-linux-gnu (x86_64) 2 | -- 3 | -- Host: localhost Database: admin_dev 4 | -- ------------------------------------------------------ 5 | -- Server version 10.1.26-MariaDB-0+deb9u1 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE = @@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE = '+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */; 15 | /*!40101 SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; 17 | 18 | -- 19 | -- Table structure for table `account_role` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `account_role`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `account_role` 26 | ( 27 | `account_id` bigint(20) unsigned NOT NULL COMMENT '用户Id', 28 | `role_id` bigint(20) unsigned NOT NULL COMMENT '角色Id', 29 | PRIMARY KEY (`account_id`, `role_id`), 30 | KEY `role_id` (`role_id`), 31 | CONSTRAINT `account_role_fk_1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 32 | CONSTRAINT `account_role_fk_2` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 33 | ) ENGINE = InnoDB 34 | DEFAULT CHARSET = utf8mb4 COMMENT ='用户角色表'; 35 | /*!40101 SET character_set_client = @saved_cs_client */; 36 | 37 | -- 38 | -- Dumping data for table `account_role` 39 | -- 40 | 41 | LOCK TABLES `account_role` WRITE; 42 | /*!40000 ALTER TABLE `account_role` 43 | DISABLE KEYS */; 44 | INSERT INTO `account_role` 45 | VALUES (1, 1); 46 | INSERT INTO `account_role` 47 | VALUES (2, 2); 48 | INSERT INTO `account_role` 49 | VALUES (3, 3); 50 | /*!40000 ALTER TABLE `account_role` 51 | ENABLE KEYS */; 52 | UNLOCK TABLES; 53 | /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; 54 | 55 | /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; 56 | /*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */; 57 | /*!40014 SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS */; 58 | /*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */; 59 | /*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */; 60 | /*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */; 61 | /*!40111 SET SQL_NOTES = @OLD_SQL_NOTES */; 62 | 63 | -- Dump completed on 2018-02-16 19:26:13 64 | -------------------------------------------------------------------------------- /api/src/test/resources/sql/dev/role_permission.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.16 Distrib 10.1.26-MariaDB, for debian-linux-gnu (x86_64) 2 | -- 3 | -- Host: localhost Database: admin_dev 4 | -- ------------------------------------------------------ 5 | -- Server version 10.1.26-MariaDB-0+deb9u1 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE = @@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE = '+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */; 15 | /*!40101 SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; 17 | 18 | -- 19 | -- Table structure for table `role_permission` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `role_permission`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `role_permission` 26 | ( 27 | `role_id` bigint(20) unsigned NOT NULL COMMENT '角色Id', 28 | `permission_id` bigint(20) unsigned NOT NULL COMMENT '权限Id', 29 | PRIMARY KEY (`role_id`, `permission_id`), 30 | KEY `permission_id` (`permission_id`), 31 | CONSTRAINT `role_permission_fk_1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 32 | CONSTRAINT `role_permission_fk_2` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 33 | ) ENGINE = InnoDB 34 | DEFAULT CHARSET = utf8mb4 COMMENT ='角色权限表'; 35 | /*!40101 SET character_set_client = @saved_cs_client */; 36 | 37 | -- 38 | -- Dumping data for table `role_permission` 39 | -- 40 | 41 | LOCK TABLES `role_permission` WRITE; 42 | /*!40000 ALTER TABLE `role_permission` 43 | DISABLE KEYS */; 44 | INSERT INTO `role_permission` 45 | VALUES (3, 1); 46 | INSERT INTO `role_permission` 47 | VALUES (3, 5); 48 | /*!40000 ALTER TABLE `role_permission` 49 | ENABLE KEYS */; 50 | UNLOCK TABLES; 51 | /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; 52 | 53 | /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; 54 | /*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */; 55 | /*!40014 SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS */; 56 | /*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */; 57 | /*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */; 58 | /*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */; 59 | /*!40111 SET SQL_NOTES = @OLD_SQL_NOTES */; 60 | 61 | -- Dump completed on 2018-02-16 19:26:13 62 | -------------------------------------------------------------------------------- /admin/src/store/modules/account.js: -------------------------------------------------------------------------------- 1 | import { login, logout, detail } from '@/api/account' 2 | import { getToken, setToken, removeToken } from '@/utils/token' 3 | 4 | const account = { 5 | state: { 6 | token: getToken(), 7 | accountId: -1, 8 | email: null, 9 | name: null, 10 | loginTime: -1, 11 | registerTime: -1, 12 | roleName: null, 13 | permissionCodeList: [] 14 | }, 15 | 16 | mutations: { 17 | SET_TOKEN: (state, token) => { 18 | state.token = token 19 | }, 20 | SET_ACCOUNT: (state, account) => { 21 | state.accountId = account.id 22 | state.email = account.email 23 | state.name = account.name 24 | state.loginTime = account.loginTime 25 | state.registerTime = account.registerTime 26 | state.roleName = account.roleName 27 | state.permissionCodeList = account.permissionCodeList 28 | }, 29 | RESET_ACCOUNT: (state) => { 30 | state.token = null 31 | state.accountId = -1 32 | state.email = null 33 | state.name = null 34 | state.loginTime = -1 35 | state.registerTime = -1 36 | state.roleName = null 37 | state.permissionCodeList = [] 38 | } 39 | }, 40 | 41 | actions: { 42 | // 登录 43 | Login({ commit }, loginForm) { 44 | return new Promise((resolve, reject) => { 45 | login(loginForm).then(response => { 46 | if (response.code === 200) { 47 | // cookie中保存token 48 | setToken(response.data) 49 | // vuex中保存token 50 | commit('SET_TOKEN', response.data) 51 | } 52 | // 传递给/login/index.vue : store.dispatch('Login').then(data) 53 | resolve(response) 54 | }).catch(error => { 55 | reject(error) 56 | }) 57 | }) 58 | }, 59 | 60 | // 获取用户信息 61 | Detail({ commit }) { 62 | return new Promise((resolve, reject) => { 63 | detail().then(response => { 64 | // 储存用户信息 65 | commit('SET_ACCOUNT', response.data) 66 | resolve(response) 67 | }).catch(error => { 68 | reject(error) 69 | }) 70 | }) 71 | }, 72 | 73 | // 登出 74 | Logout({ commit }) { 75 | return new Promise((resolve, reject) => { 76 | logout().then(() => { 77 | // 清除token等相关角色信息 78 | commit('RESET_ACCOUNT') 79 | removeToken() 80 | resolve() 81 | }).catch(error => { 82 | reject(error) 83 | }) 84 | }) 85 | }, 86 | 87 | // 前端 登出 88 | FedLogout({ commit }) { 89 | return new Promise(resolve => { 90 | commit('RESET_ACCOUNT') 91 | removeToken() 92 | resolve() 93 | }) 94 | } 95 | } 96 | } 97 | 98 | export default account 99 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/aspect/ControllerLogAspect.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.aspect; 2 | 3 | import com.zoctan.api.util.IpUtils; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.aspectj.lang.JoinPoint; 6 | import org.aspectj.lang.annotation.*; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.context.request.RequestContextHolder; 9 | import org.springframework.web.context.request.ServletRequestAttributes; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.time.LocalDateTime; 13 | import java.time.temporal.ChronoUnit; 14 | import java.util.Arrays; 15 | 16 | /** 17 | * controller日志切面 18 | * 19 | * @author Zoctan 20 | * @date 2018/07/13 21 | */ 22 | @Aspect 23 | @Slf4j 24 | @Component 25 | public class ControllerLogAspect { 26 | /** 开始时间 */ 27 | private LocalDateTime startTime; 28 | 29 | @Pointcut("execution(* com.zoctan.api.controller..*.*(..))") 30 | public void controllers() {} 31 | 32 | @Before("controllers()") 33 | public void deBefore(final JoinPoint joinPoint) { 34 | // 接收到请求,记录请求内容 35 | log.debug("==========================================================="); 36 | log.debug("================ Controller Log Start ==================="); 37 | log.debug("==========================================================="); 38 | this.startTime = LocalDateTime.now(); 39 | final ServletRequestAttributes attributes = 40 | (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 41 | if (attributes != null) { 42 | final HttpServletRequest request = attributes.getRequest(); 43 | log.debug("==> Request: [{}]{}", request.getMethod(), request.getRequestURL()); 44 | log.debug("==> From IP: {}", IpUtils.getIpAddress()); 45 | } 46 | log.debug( 47 | "==> Method: {}", 48 | joinPoint.getSignature().getDeclaringTypeName() + "#" + joinPoint.getSignature().getName()); 49 | log.debug("==> Args: {}", Arrays.toString(joinPoint.getArgs())); 50 | } 51 | 52 | /** 53 | * 后置结果返回 54 | * 55 | * @param result 结果 56 | */ 57 | @AfterReturning(pointcut = "controllers()", returning = "result") 58 | public void doAfterReturning(final Object result) { 59 | // 处理请求的时间差 60 | final long difference = ChronoUnit.MILLIS.between(this.startTime, LocalDateTime.now()); 61 | log.debug("==> Spend: {}s", difference / 1000.0); 62 | log.debug("==> Return: {}", result); 63 | log.debug("================ Controller Log End ====================="); 64 | } 65 | 66 | /** 后置异常通知 */ 67 | @AfterThrowing(pointcut = "controllers()", throwing = "e") 68 | public void doAfterThrowing(final Throwable e) { 69 | log.debug("==> Exception: {}", e.toString()); 70 | e.printStackTrace(); 71 | log.debug("================ Controller Log End ====================="); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /api/src/test/java/RsaEncryptor.java: -------------------------------------------------------------------------------- 1 | import com.zoctan.api.core.rsa.RsaUtils; 2 | import org.apache.commons.codec.binary.Base64; 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.security.KeyPair; 7 | import java.security.PrivateKey; 8 | import java.security.PublicKey; 9 | 10 | /** 11 | * RSA工具测试 12 | * 13 | * @author Zoctan 14 | * @date 2018/05/27 15 | */ 16 | public class RsaEncryptor { 17 | private final RsaUtils rsaUtil = new RsaUtils(); 18 | 19 | /** 加载公私钥pem格式文件测试 */ 20 | @Test 21 | public void test1() throws Exception { 22 | final PublicKey publicKey = 23 | this.rsaUtil.loadPublicKey( 24 | Base64.decodeBase64( 25 | "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANl43G/i7U86D2pWh6UQ7whrddH9QDqTBoZjbySk3sIS2W/AoZnCwJAhYYfQtY6qZ4p9oWwH9OQC7Z/8S3W6M58CAwEAAQ==")); 26 | final PrivateKey privateKey = 27 | this.rsaUtil.loadPrivateKey( 28 | Base64.decodeBase64( 29 | "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2Xjcb+LtTzoPalaHpRDvCGt10f1AOpMGhmNvJKTewhLZb8ChmcLAkCFhh9C1jqpnin2hbAf05ALtn/xLdboznwIDAQABAkEAhc3iO4kxH9UGVRQmY352xARyOqCKWz/I/PjDEpXKZTdWs1oqTM2lpYBlpnV/fJ3Sf2es6Sv6reLCLP7MZP1KGQIhAP0+wZ0c1YBVJvkkHHmhnGyaU1Bsb1alPY4A2sM97Q0lAiEA29Z7rVhw4Fgx201uhnwb0dqiuXfCZM1+fVDyTSg0XHMCIBZencGgE2fjna6yNuWzldquAx/+hBM2Q2qwvqIybScVAiEAlFhnnNHRWZIqEpJtwtJ8819V71GhG+SPNoEpAGfg7YECIHPReHdJdfBehhD1o63xH+gTZqliK4n6XvBhkcyWNYdS")); 30 | Assert.assertNotNull(publicKey); 31 | Assert.assertNotNull(privateKey); 32 | System.out.println("公钥:" + publicKey); 33 | System.out.println("私钥:" + privateKey); 34 | 35 | final String data = "zoctan"; 36 | // 公钥加密 37 | final byte[] encrypted = this.rsaUtil.encrypt(data.getBytes()); 38 | System.out.println("加密后:" + Base64.encodeBase64String(encrypted)); 39 | 40 | // 私钥解密 41 | final byte[] decrypted = this.rsaUtil.decrypt(encrypted); 42 | System.out.println("解密后:" + new String(decrypted)); 43 | } 44 | 45 | /** 生成RSA密钥对并进行加解密测试 */ 46 | @Test 47 | public void test2() throws Exception { 48 | final String data = "hello word"; 49 | final KeyPair keyPair = this.rsaUtil.genKeyPair(512); 50 | 51 | // 获取公钥,并以base64格式打印出来 52 | final PublicKey publicKey = keyPair.getPublic(); 53 | System.out.println("公钥:" + new String(Base64.encodeBase64(publicKey.getEncoded()))); 54 | 55 | // 获取私钥,并以base64格式打印出来 56 | final PrivateKey privateKey = keyPair.getPrivate(); 57 | System.out.println("私钥:" + new String(Base64.encodeBase64(privateKey.getEncoded()))); 58 | 59 | // 公钥加密 60 | final byte[] encrypted = this.rsaUtil.encrypt(data.getBytes(), publicKey); 61 | System.out.println("加密后:" + new String(encrypted)); 62 | 63 | // 私钥解密 64 | final byte[] decrypted = this.rsaUtil.decrypt(encrypted, privateKey); 65 | System.out.println("解密后:" + new String(decrypted)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/controller/RoleController.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.controller; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.zoctan.api.core.response.Result; 6 | import com.zoctan.api.core.response.ResultGenerator; 7 | import com.zoctan.api.dto.RoleWithPermission; 8 | import com.zoctan.api.dto.RoleWithResource; 9 | import com.zoctan.api.entity.Role; 10 | import com.zoctan.api.service.RoleService; 11 | import org.springframework.security.access.prepost.PreAuthorize; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import javax.annotation.Resource; 15 | import java.security.Principal; 16 | import java.util.List; 17 | 18 | /** 19 | * @author Zoctan 20 | * @date 2018/06/09 21 | */ 22 | @RestController 23 | @RequestMapping("/role") 24 | public class RoleController { 25 | @Resource private RoleService roleService; 26 | 27 | @PreAuthorize("hasAuthority('role:add')") 28 | @PostMapping 29 | public Result add(@RequestBody final RoleWithPermission role, final Principal principal) { 30 | this.roleService.save(role); 31 | return ResultGenerator.genOkResult(); 32 | } 33 | 34 | @PreAuthorize("hasAuthority('role:delete')") 35 | @DeleteMapping("/{id}") 36 | public Result delete(@PathVariable final Long id, final Principal principal) { 37 | final Role dbRole = this.roleService.getById(id); 38 | if (dbRole == null) { 39 | return ResultGenerator.genFailedResult("角色不存在"); 40 | } 41 | this.roleService.deleteById(id); 42 | return ResultGenerator.genOkResult(); 43 | } 44 | 45 | @PreAuthorize("hasAuthority('role:update')") 46 | @PutMapping 47 | public Result update(@RequestBody final RoleWithPermission role, final Principal principal) { 48 | final Role dbRole = this.roleService.getById(role.getId()); 49 | if (dbRole == null) { 50 | return ResultGenerator.genFailedResult("角色不存在"); 51 | } 52 | this.roleService.update(role); 53 | return ResultGenerator.genOkResult(); 54 | } 55 | 56 | @PreAuthorize("hasAuthority('role:list')") 57 | @GetMapping("/permission") 58 | public Result listWithPermission( 59 | @RequestParam(defaultValue = "0") final Integer page, 60 | @RequestParam(defaultValue = "0") final Integer size) { 61 | PageHelper.startPage(page, size); 62 | final List list = this.roleService.listRoleWithPermission(); 63 | final PageInfo pageInfo = new PageInfo<>(list); 64 | return ResultGenerator.genOkResult(pageInfo); 65 | } 66 | 67 | @GetMapping 68 | public Result list( 69 | @RequestParam(defaultValue = "0") final Integer page, 70 | @RequestParam(defaultValue = "0") final Integer size) { 71 | PageHelper.startPage(page, size); 72 | final List list = this.roleService.listAll(); 73 | final PageInfo pageInfo = new PageInfo<>(list); 74 | return ResultGenerator.genOkResult(pageInfo); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /admin/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /admin/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 vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.svg$/, 56 | loader: 'svg-sprite-loader', 57 | include: [resolve('src/icons')], 58 | options: { 59 | symbolId: 'icon-[name]' 60 | } 61 | }, 62 | { 63 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 64 | loader: 'url-loader', 65 | exclude: [resolve('src/icons')], 66 | options: { 67 | limit: 10000, 68 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 69 | } 70 | }, 71 | { 72 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 77 | } 78 | }, 79 | { 80 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 81 | loader: 'url-loader', 82 | options: { 83 | limit: 10000, 84 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 85 | } 86 | } 87 | ] 88 | }, 89 | node: { 90 | // prevent webpack from injecting useless setImmediate polyfill because Vue 91 | // source contains it (although only uses it if it's native). 92 | setImmediate: false, 93 | // prevent webpack from injecting mocks to Node native modules 94 | // that does not make sense for the client 95 | dgram: 'empty', 96 | fs: 'empty', 97 | net: 'empty', 98 | tls: 'empty', 99 | child_process: 'empty' 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /api/src/test/resources/sql/dev/permission.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.16 Distrib 10.1.26-MariaDB, for debian-linux-gnu (x86_64) 2 | -- 3 | -- Host: localhost Database: admin_dev 4 | -- ------------------------------------------------------ 5 | -- Server version 10.1.26-MariaDB-0+deb9u1 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE = @@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE = '+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */; 15 | /*!40101 SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; 17 | 18 | -- 19 | -- Table structure for table `permission` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `permission`; 23 | CREATE TABLE `permission` 24 | ( 25 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '权限Id', 26 | `resource` varchar(256) NOT NULL COMMENT '权限对应的资源', 27 | `code` varchar(256) NOT NULL COMMENT '权限的代码/通配符,对应代码中@hasAuthority(xx)', 28 | `handle` varchar(256) NOT NULL COMMENT '对应的资源操作', 29 | PRIMARY KEY (`id`) 30 | ) ENGINE = InnoDB 31 | AUTO_INCREMENT = 13 32 | DEFAULT CHARSET = utf8mb4 COMMENT ='权限表'; 33 | 34 | -- 35 | -- Dumping data for table `permission` 36 | -- 37 | 38 | LOCK TABLES `permission` WRITE; 39 | /*!40000 ALTER TABLE `permission` 40 | DISABLE KEYS */; 41 | INSERT INTO `permission` 42 | VALUES (1, '账号', 'account:list', '列表'); 43 | INSERT INTO `permission` 44 | VALUES (2, '账号', 'account:detail', '详情'); 45 | INSERT INTO `permission` 46 | VALUES (3, '账号', 'account:add', '添加'); 47 | INSERT INTO `permission` 48 | VALUES (4, '账号', 'account:update', '更新'); 49 | INSERT INTO `permission` 50 | VALUES (5, '账号', 'account:delete', '删除'); 51 | INSERT INTO `permission` 52 | VALUES (6, '账号', 'account:search', '搜索'); 53 | INSERT INTO `permission` 54 | VALUES (7, '角色', 'role:list', '列表'); 55 | INSERT INTO `permission` 56 | VALUES (8, '角色', 'role:detail', '详情'); 57 | INSERT INTO `permission` 58 | VALUES (9, '角色', 'role:add', '添加'); 59 | INSERT INTO `permission` 60 | VALUES (10, '角色', 'role:update', '更新'); 61 | INSERT INTO `permission` 62 | VALUES (11, '角色', 'role:delete', '删除'); 63 | INSERT INTO `permission` 64 | VALUES (12, '角色', 'role:search', '搜索'); 65 | /*!40000 ALTER TABLE `permission` 66 | ENABLE KEYS */; 67 | UNLOCK TABLES; 68 | /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; 69 | 70 | /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; 71 | /*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */; 72 | /*!40014 SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS */; 73 | /*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */; 74 | /*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */; 75 | /*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */; 76 | /*!40111 SET SQL_NOTES = @OLD_SQL_NOTES */; 77 | 78 | -- Dump completed on 2018-02-16 20:28:17 79 | -------------------------------------------------------------------------------- /api/src/main/resources/mapper/AccountMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 66 | 67 | 68 | UPDATE account 69 | SET login_time = NOW() 70 | WHERE name = #{name} 71 | 72 | 73 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/service/Service.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.service; 2 | 3 | import com.zoctan.api.core.exception.ResourcesNotFoundException; 4 | import org.apache.ibatis.exceptions.TooManyResultsException; 5 | import tk.mybatis.mapper.entity.Condition; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Service 层基础接口 11 | * 12 | * @author Zoctan 13 | * @date 2018/05/27 14 | */ 15 | public interface Service { 16 | 17 | /** 18 | * 确保实体存在 19 | * 20 | * @param id 实体id 21 | * @throws ResourcesNotFoundException 不存在实体异常 22 | */ 23 | void assertById(Object id); 24 | 25 | /** 26 | * 确保实体存在 27 | * 28 | * @param entity 实体 29 | * @throws ResourcesNotFoundException 不存在实体异常 30 | */ 31 | void assertBy(T entity); 32 | 33 | /** 34 | * 确保实体存在 35 | * 36 | * @param ids ids 37 | */ 38 | void assertByIds(String ids); 39 | 40 | /** 41 | * 根据 ids 获取实体数 42 | * 43 | * @param ids ids 44 | */ 45 | int countByIds(String ids); 46 | 47 | /** 48 | * 根据条件获取实体数 49 | * 50 | * @param condition 条件 51 | */ 52 | int countByCondition(Condition condition); 53 | 54 | /** 55 | * 持久化 56 | * 57 | * @param entity 实体 58 | */ 59 | void save(T entity); 60 | 61 | /** 62 | * 批量持久化 63 | * 64 | * @param entities 实体列表 65 | */ 66 | void save(List entities); 67 | 68 | /** 69 | * 通过主鍵刪除 70 | * 71 | * @param id id 72 | */ 73 | void deleteById(Object id); 74 | 75 | /** 76 | * 通过实体中某个成员变量名称(非数据表中 column 的名称)刪除 77 | * 78 | * @param fieldName 字段名 79 | * @param value 字段值 80 | * @throws TooManyResultsException 多条结果异常 81 | */ 82 | void deleteBy(String fieldName, Object value) throws TooManyResultsException; 83 | 84 | /** 85 | * 批量刪除 ids -> “1,2,3,4” 86 | * 87 | * @param ids ids 88 | */ 89 | void deleteByIds(String ids); 90 | 91 | /** 92 | * 根据条件刪除 93 | * 94 | * @param condition 条件 95 | */ 96 | void deleteByCondition(Condition condition); 97 | 98 | /** 99 | * 按组件更新 100 | * 101 | * @param entity 实体 102 | */ 103 | void update(T entity); 104 | 105 | /** 106 | * 按条件更新 107 | * 108 | * @param entity 实体 109 | * @param condition 条件 110 | */ 111 | void updateByCondition(T entity, Condition condition); 112 | 113 | /** 114 | * 通过 id 查找 115 | * 116 | * @param id id 117 | * @return 实体 118 | */ 119 | T getById(Object id); 120 | 121 | /** 122 | * 通过实体中某个成员变量名称查找 value 需符合 unique 约束 123 | * 124 | * @param fieldName 字段名 125 | * @param value 字段值 126 | * @return 实体 127 | * @throws TooManyResultsException 多条结果异常 128 | */ 129 | T getBy(String fieldName, Object value) throws TooManyResultsException; 130 | 131 | /** 132 | * 通过多个 id 查找 ids -> “1,2,3,4” 133 | * 134 | * @param ids ids 135 | * @return 实体列表 136 | */ 137 | List listByIds(String ids); 138 | 139 | /** 140 | * 按条件查找 141 | * 142 | * @param condition 条件 143 | * @return 实体列表 144 | */ 145 | List listByCondition(Condition condition); 146 | 147 | /** 148 | * 获取所有实体 149 | * 150 | * @return 实体列表 151 | */ 152 | List listAll(); 153 | } 154 | -------------------------------------------------------------------------------- /api/src/test/resources/sql/dev/account.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.16 Distrib 10.1.26-MariaDB, for debian-linux-gnu (x86_64) 2 | -- 3 | -- Host: localhost Database: admin_dev 4 | -- ------------------------------------------------------ 5 | -- Server version 10.1.26-MariaDB-0+deb9u1 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE = @@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE = '+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */; 15 | /*!40101 SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; 17 | 18 | -- 19 | -- Table structure for table `account` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `account`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `account` 26 | ( 27 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户Id', 28 | `email` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin COMMENT '邮箱', 29 | `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户名', 30 | `password` varchar(512) CHARACTER SET utf8 COLLATE utf8_bin COMMENT '密码', 31 | `register_time` datetime DEFAULT NOW() COMMENT '注册时间', 32 | `login_time` datetime DEFAULT NULL COMMENT '上一次登录时间', 33 | PRIMARY KEY (`id`), 34 | UNIQUE KEY `ix_account_name` (`name`), 35 | UNIQUE KEY `ix_account_email` (`email`) 36 | ) ENGINE = InnoDB 37 | AUTO_INCREMENT = 4 38 | DEFAULT CHARSET = utf8mb4 COMMENT ='用户表'; 39 | /*!40101 SET character_set_client = @saved_cs_client */; 40 | 41 | -- 42 | -- Dumping data for table `account` 43 | -- 44 | 45 | LOCK TABLES `account` WRITE; 46 | /*!40000 ALTER TABLE `account` 47 | DISABLE KEYS */; 48 | INSERT INTO `account` 49 | VALUES (1, 'admin@qq.com', 'admin', '$2a$10$wg0f10n.30UbU.9hPpucbef/ya62LdTKs72xJfjxvTFsL0Xaewbra', 50 | '2019-07-01 00:00:00', '2019-07-01 00:00:00'); 51 | INSERT INTO `account` 52 | VALUES (2, 'editor@qq.com', 'editor', '$2a$10$/m4SgZ.ZFVZ7rcbQpJW2tezmuhf/UzQtpAtXb0WZiAE3TeHxq2DYu', 53 | '2019-07-02 00:00:00', '2019-07-02 00:00:00'); 54 | INSERT INTO `account` 55 | VALUES (3, 'test@qq.com', 'test', '$2a$10$.0gBYBHAtdkxUUQNg3kII.fqGOngF4BLe8JavthZFalt2QIXHlrhm', 56 | '2019-07-03 00:00:00', '2019-07-03 00:00:00'); 57 | /*!40000 ALTER TABLE `account` 58 | ENABLE KEYS */; 59 | UNLOCK TABLES; 60 | /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; 61 | 62 | /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; 63 | /*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */; 64 | /*!40014 SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS */; 65 | /*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */; 66 | /*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */; 67 | /*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */; 68 | /*!40111 SET SQL_NOTES = @OLD_SQL_NOTES */; 69 | 70 | -- Dump completed on 2018-02-16 19:24:53 71 | -------------------------------------------------------------------------------- /api/src/main/java/com/zoctan/api/core/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.zoctan.api.core.config; 2 | 3 | import com.alibaba.fastjson.serializer.SerializerFeature; 4 | import com.alibaba.fastjson.support.config.FastJsonConfig; 5 | import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.http.converter.HttpMessageConverter; 9 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 10 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 12 | 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Spring MVC 配置 19 | * 20 | * @author Zoctan 21 | * @date 2018/05/27 22 | */ 23 | @Configuration 24 | public class WebMvcConfig extends WebMvcConfigurationSupport { 25 | /** 使用阿里 FastJson 作为 JSON MessageConverter */ 26 | @Override 27 | public void configureMessageConverters(final List> converters) { 28 | final FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); 29 | final FastJsonConfig config = new FastJsonConfig(); 30 | converter.setSupportedMediaTypes( 31 | new ArrayList() { 32 | private static final long serialVersionUID = 1924772982095119650L; 33 | 34 | { 35 | this.add(MediaType.APPLICATION_JSON_UTF8); 36 | this.add(MediaType.APPLICATION_OCTET_STREAM); 37 | this.add(MediaType.IMAGE_GIF); 38 | this.add(MediaType.IMAGE_JPEG); 39 | this.add(MediaType.IMAGE_PNG); 40 | } 41 | }); 42 | config.setSerializerFeatures( 43 | // 保留空的字段 44 | // SerializerFeature.WriteMapNullValue, 45 | // Number null -> 0 46 | SerializerFeature.WriteNullNumberAsZero, 47 | // 美化输出 48 | SerializerFeature.PrettyFormat); 49 | converter.setFastJsonConfig(config); 50 | converter.setDefaultCharset(StandardCharsets.UTF_8); 51 | converters.add(converter); 52 | } 53 | 54 | /** 视图控制器 */ 55 | @Override 56 | public void addViewControllers(final ViewControllerRegistry registry) { 57 | // solved swagger2 58 | registry.addRedirectViewController("/v2/api-docs", "/v2/api-docs?group=restful-api"); 59 | registry.addRedirectViewController( 60 | "/swagger-resources/configuration/ui", "/swagger-resources/configuration/ui"); 61 | registry.addRedirectViewController( 62 | "/swagger-resources/configuration/security", "/swagger-resources/configuration/security"); 63 | registry.addRedirectViewController("/swagger-resources", "/swagger-resources"); 64 | } 65 | 66 | /** 资源控制器 */ 67 | @Override 68 | public void addResourceHandlers(final ResourceHandlerRegistry registry) { 69 | // solved swagger2 70 | registry 71 | .addResourceHandler("/swagger-ui.html**") 72 | .addResourceLocations("classpath:/META-INF/resources/swagger-ui.html"); 73 | registry 74 | .addResourceHandler("/webjars/**") 75 | .addResourceLocations("classpath:/META-INF/resources/webjars/"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /admin/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Vue Admin 2 | 3 | Provides a set of background permission management templates that separate the front and back ends. 4 | 5 | ![stars](https://img.shields.io/github/stars/Zoctan/spring-boot-vue-admin.svg?style=flat-square&label=Stars) 6 | ![license](https://img.shields.io/github/license/Zoctan/spring-boot-vue-admin.svg?style=flat-square) 7 | 8 | English | [简体中文](./README-zh.md) 9 | 10 | Front-end ideas reference ["Hand touch, take you to use vue to touch the background series II (login authority)"](https://juejin.im/post/591aa14f570c35006961acac), the template comes from [vue-element-admin](https: //github.com/PanJiaChen/vue-element-admin), other functions can be expanded according to this project. 11 | 12 | Back-end ideas reference ["Role-Based Access Control New Solution"](http://globeeip.iteye.com/blog/1236167), the template comes from [spring-boot-api-seedling](https://github.com/Zoctan/spring-boot-api-seedling.git), please see the api's [README](https://github.com/Zoctan/spring-boot-vue-admin/tree/master/api) for design ideas. 13 | 14 | Note: Since the front-end has not been updated for several years, there are loopholes and outdated versions. If necessary, please refer to the new project written in Vue3: [admin-vue3-template](https://github.com/Zoctan/admin-vue3-template)。 15 | 16 | Welcome friends to star and issues ~ thank you :) 17 | 18 | # Preview 19 | 20 | ![role list](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/1.png) 21 | 22 | ![role manage](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/2.png) 23 | 24 | ![user manage](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/3.png) 25 | 26 | ![user role manage](https://github.com/Zoctan/spring-boot-vue-admin/blob/master/README/4.png) 27 | 28 | # Dependency version 29 | 30 | frontend | version 31 | --------|------ 32 | node | 8.16.1 33 | npm | 6.4.1 34 | 35 | backend | version 36 | -----------|------ 37 | SpringBoot | 2.1.6 38 | 39 | # Quick start 40 | 41 | ```markdown 42 | # clone project 43 | git clone https://github.com/Zoctan/spring-boot-vue-admin.git 44 | 45 | # go to project 46 | cd spring-boot-vue-admin 47 | 48 | # go to backend 49 | cd api 50 | 51 | # import database sql files (Remember to modify the database information) 52 | sudo chmod a+x resetDB.sh && ./resetDB.sh 53 | 54 | # start the backend ... 55 | 56 | # go to frontend 57 | cd app 58 | 59 | # install dependency 60 | npm install 61 | 62 | # start the frontend ... 63 | npm run dev 64 | ``` 65 | 66 | # Problem solve 67 | 68 | ## no such file/ansi-styles/css-loader 69 | 70 | ```bash 71 | npm ERR! enoent ENOENT: no such file or directory, rename '/workspace/spring-boot-vue-admin/app/node_modules/.staging/css-loader-b931fe48/node_modules/ansi-styles' -> '/workspace/spring-boot-vue-admin/app/node_modules/.staging/ansi-styles-6535fafb' 72 | ``` 73 | 74 | please install css-loader firstly: `npm install css-loader`, and install project dependency secondly: `npm install`. 75 | 76 | # Update log 77 | 78 | 2019-10-16 The webpack version is rolled back, and there is no time to fix the new version. Update the discovered issues and add services exactly according to the backend template [spring-boot-api-seedling](https://github.com/Zoctan/spring-boot-api-seedling.git). 79 | 80 | ~~2018-06-10 Redis is removed because Redis is mainly used as a cache database, but it does not play much role in this project. Note that if you need to make the token invalid during logout, you need to use Redis together, you can add it according to the backend template. ~~ 81 | -------------------------------------------------------------------------------- /admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin", 3 | "version": "1.0.0", 4 | "description": "admin", 5 | "author": "Zoctan", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "prod": "webpack-dev-server --inline --progress --config build/webpack.prod.conf.js", 10 | "start": "npm run dev", 11 | "unit": "jest --config test/unit/jest.conf.js --coverage", 12 | "e2e": "node test/e2e/runner.js", 13 | "test": "npm run unit && npm run e2e", 14 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", 15 | "build": "node build/build.js" 16 | }, 17 | "dependencies": { 18 | "vue": "^2.5.2", 19 | "vue-router": "^3.0.1", 20 | "vuex": "^3.0.1" 21 | }, 22 | "devDependencies": { 23 | "css-loader": "^0.28.11", 24 | "file-loader": "^1.1.4", 25 | "sass-loader": "^7.0.3", 26 | "url-loader": "^0.5.8", 27 | "autoprefixer": "^7.1.2", 28 | "axios": "^0.19.0", 29 | "babel-core": "^6.22.1", 30 | "babel-eslint": "^8.2.1", 31 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 32 | "babel-jest": "^21.0.2", 33 | "babel-loader": "^7.1.1", 34 | "babel-plugin-dynamic-import-node": "^1.2.0", 35 | "babel-plugin-syntax-jsx": "^6.18.0", 36 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 37 | "babel-plugin-transform-runtime": "^6.22.0", 38 | "babel-plugin-transform-vue-jsx": "^3.5.0", 39 | "babel-preset-env": "^1.3.2", 40 | "babel-preset-stage-2": "^6.22.0", 41 | "babel-register": "^6.22.0", 42 | "chalk": "^2.0.1", 43 | "connect-history-api-fallback": "^1.5.0", 44 | "copy-webpack-plugin": "^4.0.1", 45 | "cross-spawn": "^5.0.1", 46 | "element-ui": "^2.12.0", 47 | "eslint": "^4.15.0", 48 | "eslint-config-standard": "^10.2.1", 49 | "eslint-friendly-formatter": "^3.0.0", 50 | "eslint-loader": "^1.7.1", 51 | "eslint-plugin-html": "^4.0.3", 52 | "eslint-plugin-import": "^2.7.0", 53 | "eslint-plugin-node": "^5.2.0", 54 | "eslint-plugin-promise": "^3.4.0", 55 | "eslint-plugin-standard": "^3.0.1", 56 | "eslint-plugin-vue": "^4.0.0", 57 | "eventsource-polyfill": "^0.9.6", 58 | "express": "^4.16.3", 59 | "extract-text-webpack-plugin": "^3.0.0", 60 | "friendly-errors-webpack-plugin": "^1.6.1", 61 | "html-webpack-plugin": "^2.30.1", 62 | "http-proxy-middleware": "^0.18.0", 63 | "jest": "^22.0.4", 64 | "jest-serializer-vue": "^0.3.0", 65 | "js-cookie": "^2.2.0", 66 | "nightwatch": "^0.9.12", 67 | "node-notifier": "^5.1.2", 68 | "node-sass": "^4.9.0", 69 | "normalize.css": "^8.0.0", 70 | "nprogress": "^0.2.0", 71 | "opn": "^5.3.0", 72 | "optimize-css-assets-webpack-plugin": "^3.2.0", 73 | "ora": "^1.2.0", 74 | "portfinder": "^1.0.13", 75 | "postcss-import": "^11.0.0", 76 | "postcss-loader": "^2.0.8", 77 | "postcss-url": "^7.2.1", 78 | "rimraf": "^2.6.0", 79 | "semver": "^5.3.0", 80 | "shelljs": "^0.7.6", 81 | "style-loader": "^0.21.0", 82 | "svg-sprite-loader": "^3.8.0", 83 | "uglifyjs-webpack-plugin": "^1.1.1", 84 | "vue-jest": "^1.0.2", 85 | "vue-loader": "^13.3.0", 86 | "vue-style-loader": "^3.0.1", 87 | "vue-template-compiler": "^2.5.2", 88 | "webpack": "^3.6.0", 89 | "webpack-bundle-analyzer": "^2.9.0", 90 | "webpack-dev-server": "^2.9.1", 91 | "webpack-hot-middleware": "^2.22.2", 92 | "webpack-merge": "^4.1.0" 93 | }, 94 | "engines": { 95 | "node": ">= 6.0.0", 96 | "npm": ">= 3.0.0" 97 | }, 98 | "browserslist": [ 99 | "> 1%", 100 | "last 2 versions", 101 | "not ie <= 8" 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # RESTFul API 2 | 3 | 主要介绍后端 API 的角色权限控制。 4 | 5 | ## 数据库设计 6 | 7 | 数据库主要包含[五张表](https://github.com/Zoctan/spring-boot-vue-admin/tree/master/api/src/test/resources/dev/sql),分别是用户表 user、角色表 role、用户角色表 user_role、权限表 permission、角色权限表 role_permission。 8 | 9 | 数据库关系模型如下: 10 | 11 | 12 | 13 | user 表:用户信息。 14 | 15 | 16 | 17 | role 表:角色信息。 18 | 19 | 20 | 21 | user_role 表:用户对应的角色,一对一。 22 | 23 | 24 | 25 | permission 表:权限能操作的资源以及操作方式。 26 | 27 | 28 | 29 | role_permission 表:角色所对应的权限,一对多。 30 | 31 | 32 | 33 | > 为什么 ROLE_ADMIN 角色在数据库没有权限? 34 | > 35 | > ROLE_ADMIN 作为超级管理员这类角色,应该是具有所有权限的,但是对于数据库来说,没必要保存所有权限,只要在查询到该角色时返回所有权限即可。 36 | 37 | ## 角色权限控制 38 | 39 | Spring Security + Json Web Token 鉴权: 40 | 41 | 最终效果,在控制器上的注解: 42 | 43 | ```java 44 | @PreAuthorize("hasAuthority('user:list')") 45 | ``` 46 | 47 | 实现思路:用户登录 -> 服务端生成 token -> 客户端保存 token,之后的每次请求都携带该 token,服务端认证解析。 48 | 49 | 所以在服务端认证解析的 token 就要保存有用户的角色和相应的权限: 50 | 51 | ```java 52 | // service/impl/UserDetailsServiceImpl.java 53 | 54 | // 为了方便,角色和权限都放在一起 55 | // 权限 56 | List authorities = 57 | user.getPermissionCodeList().stream() 58 | .map(SimpleGrantedAuthority::new) 59 | .collect(Collectors.toList()); 60 | // 角色 61 | authorities.add(new SimpleGrantedAuthority(user.getRoleName())); 62 | // [ROLE_TEST, role:list, user:list] 63 | ``` 64 | 65 | JWT 生成 token: 66 | 67 | ```java 68 | // core/jwt/JwtUtil.java 69 | 70 | Jwts.builder() 71 | // 设置用户名 72 | .setSubject(username) 73 | // 添加权限属性 74 | .claim(this.AUTHORITIES_KEY, authorities) 75 | // 设置失效时间 76 | .setExpiration(date) 77 | // 私钥加密生成签名 78 | .signWith(SignatureAlgorithm.RS256, privateKey) 79 | .compact(); 80 | ``` 81 | 82 | Base64 解码 JWT 生成的 token: 83 | 84 | ``` 85 | {"alg":"RS256"}{"sub":"test","auth":"ROLE_TEST,role:list,user:list,"exp":1519742226}