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 |
2 |
3 |
4 | {{ item.name }}
9 | {{ item.name }}
10 |
11 |
12 |
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 |
2 |
3 |
4 |
9 |
10 |
11 | {{ item.children[0].name }}
12 |
13 |
14 |
15 |
16 |
17 | {{ item.name }}
18 |
19 |
20 |
21 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
47 |
48 |
58 |
--------------------------------------------------------------------------------
/admin/src/views/layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
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 |
2 |
3 |
28 |
29 |
30 |
31 |
46 |
47 |
62 |
--------------------------------------------------------------------------------
/admin/src/views/layout/components/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ name }}
8 |
9 |
10 |
11 |
12 | 账户中心
13 |
14 |
15 | 注销
16 |
17 |
18 |
19 |
20 |
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 |
2 |
3 |
返回
4 |
5 |
6 | Oops!
gif来源
7 | airbnb 页面
8 | 你没有权限去该页面
9 | 如有不满请联系你领导
10 |
11 | - 或者你可以去:
12 | -
13 | 回首页
14 |
15 | -
16 | 随便看看
17 |
18 | -
19 | 点我看图
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
57 |
58 |
94 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | # Spring Boot Vue Admin
2 |
3 | 提供一套前后端分离的后台权限管理模版。
4 |
5 | 
6 | 
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 | 
21 |
22 | 
23 |
24 | 
25 |
26 | 
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 | 
6 | 
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 | 
21 |
22 | 
23 |
24 | 
25 |
26 | 
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}