├── front ├── .env.development ├── .env.production ├── public │ ├── favicon.ico │ └── index.html ├── babel.config.js ├── src │ ├── assets │ │ ├── avatar.gif │ │ └── login_bg.jpg │ ├── http │ │ ├── http.js │ │ ├── modules │ │ │ ├── login.js │ │ │ └── menu.js │ │ ├── auth.js │ │ └── httpRequest.js │ ├── views │ │ ├── menu │ │ │ ├── Echarts.vue │ │ │ ├── Ueditor.vue │ │ │ └── HomePage.vue │ │ ├── dynamic │ │ │ ├── sys │ │ │ │ ├── UserList.vue │ │ │ │ ├── MenuControl.vue │ │ │ │ └── RoleControl.vue │ │ │ └── DynamicMenu.vue │ │ ├── home │ │ │ ├── Content.vue │ │ │ ├── Setup.vue │ │ │ ├── UpdatePassword.vue │ │ │ ├── Tab.vue │ │ │ ├── Header.vue │ │ │ └── Aside.vue │ │ └── Home.vue │ ├── store │ │ ├── index.js │ │ └── module │ │ │ ├── user.js │ │ │ └── common.js │ ├── utils │ │ └── validate.js │ ├── i18n │ │ ├── index.js │ │ └── languages │ │ │ ├── zh.json │ │ │ └── en.json │ ├── mock │ │ ├── modules │ │ │ ├── login.js │ │ │ └── menu.js │ │ └── index.js │ ├── App.vue │ ├── main.js │ ├── components │ │ └── common │ │ │ ├── 404.vue │ │ │ └── Login.vue │ └── router │ │ └── index.js ├── .gitignore ├── vue.config.js ├── package.json └── README.md ├── README.md └── back ├── src ├── main │ ├── java │ │ └── com │ │ │ └── lyh │ │ │ └── admin_template │ │ │ └── back │ │ │ ├── common │ │ │ ├── validator │ │ │ │ └── group │ │ │ │ │ ├── AddGroup.java │ │ │ │ │ ├── UpdateGroup.java │ │ │ │ │ └── sys │ │ │ │ │ ├── LoginGroup.java │ │ │ │ │ └── RegisterGroup.java │ │ │ ├── utils │ │ │ │ ├── ExceptionUtil.java │ │ │ │ ├── GsonUtil.java │ │ │ │ ├── MessageSourceUtil.java │ │ │ │ ├── MD5Util.java │ │ │ │ ├── SmsUtil.java │ │ │ │ ├── JwtUtil.java │ │ │ │ ├── MailUtil.java │ │ │ │ ├── Result.java │ │ │ │ └── OssUtil.java │ │ │ ├── config │ │ │ │ ├── MyBatisPlusConfig.java │ │ │ │ ├── LocaleConfig.java │ │ │ │ ├── SwaggerConfig.java │ │ │ │ ├── RedisConfig.java │ │ │ │ └── JWTConfig.java │ │ │ └── exception │ │ │ │ ├── GlobalException.java │ │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── mapper │ │ │ ├── xml │ │ │ │ └── UserMapper.xml │ │ │ └── UserMapper.java │ │ │ ├── modules │ │ │ ├── oss │ │ │ │ ├── mapper │ │ │ │ │ ├── xml │ │ │ │ │ │ └── BackOssMapper.xml │ │ │ │ │ └── BackOssMapper.java │ │ │ │ ├── service │ │ │ │ │ ├── BackOssService.java │ │ │ │ │ └── impl │ │ │ │ │ │ └── BackOssServiceImpl.java │ │ │ │ ├── entity │ │ │ │ │ └── BackOss.java │ │ │ │ └── controller │ │ │ │ │ └── BackOssController.java │ │ │ ├── sys │ │ │ │ ├── mapper │ │ │ │ │ ├── xml │ │ │ │ │ │ ├── SysRoleMapper.xml │ │ │ │ │ │ ├── SysUserMapper.xml │ │ │ │ │ │ └── SysUserRoleMapper.xml │ │ │ │ │ ├── SysUserMapper.java │ │ │ │ │ ├── SysRoleMapper.java │ │ │ │ │ └── SysUserRoleMapper.java │ │ │ │ ├── vo │ │ │ │ │ ├── JwtVo.java │ │ │ │ │ ├── NamePwdLoginVo.java │ │ │ │ │ ├── PhoneCodeLoginVo.java │ │ │ │ │ ├── PhonePwdLoginVo.java │ │ │ │ │ └── RegisterVo.java │ │ │ │ ├── service │ │ │ │ │ ├── SysRoleService.java │ │ │ │ │ ├── SysUserRoleService.java │ │ │ │ │ ├── SysUserService.java │ │ │ │ │ └── impl │ │ │ │ │ │ ├── SysRoleServiceImpl.java │ │ │ │ │ │ ├── SysUserRoleServiceImpl.java │ │ │ │ │ │ └── SysUserServiceImpl.java │ │ │ │ ├── controller │ │ │ │ │ ├── SysRoleController.java │ │ │ │ │ ├── SysUserRoleController.java │ │ │ │ │ └── SysUserController.java │ │ │ │ └── entity │ │ │ │ │ ├── SysUserRole.java │ │ │ │ │ ├── SysRole.java │ │ │ │ │ └── SysUser.java │ │ │ └── sms │ │ │ │ ├── entity │ │ │ │ └── SmsResponse.java │ │ │ │ └── controller │ │ │ │ └── TestSMSController.java │ │ │ ├── service │ │ │ ├── UserService.java │ │ │ └── impl │ │ │ │ └── UserServiceImpl.java │ │ │ ├── BackApplication.java │ │ │ ├── controller │ │ │ ├── UserController.java │ │ │ ├── test │ │ │ │ ├── TestJWTController.java │ │ │ │ └── TestMailController.java │ │ │ └── TestController.java │ │ │ ├── vo │ │ │ └── MailVo.java │ │ │ ├── handler │ │ │ └── MyMetaObjectHandler.java │ │ │ └── entity │ │ │ └── User.java │ └── resources │ │ ├── static │ │ ├── i18n │ │ │ ├── messages_zh_CN.properties │ │ │ └── messages_en_US.properties │ │ ├── sql │ │ │ ├── back_oss.sql │ │ │ ├── back_config.sql │ │ │ ├── back_user.sql │ │ │ └── sys │ │ │ │ ├── sys_role.sql │ │ │ │ ├── sys_user_role.sql │ │ │ │ ├── sys_user.sql │ │ │ │ ├── sys_menu.sql │ │ │ │ └── sys_role_menu.sql │ │ └── html │ │ │ └── upload.html │ │ ├── application.yml │ │ └── logback-spring.xml └── test │ └── java │ └── com │ └── lyh │ └── admin_template │ └── back │ ├── BackApplicationTests.java │ ├── TestAutoGenerator.java │ └── TestRedis.java ├── .gitignore ├── README.md └── pom.xml /front/.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_URL=http://localhost:8000 -------------------------------------------------------------------------------- /front/.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_URL=http://localhost:9000 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # admin-vue-template 2 | SpringBoot + Vue + ElementUI 实现一个后台管理系统模板 3 | -------------------------------------------------------------------------------- /front/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyh-man/admin-vue-template/HEAD/front/public/favicon.ico -------------------------------------------------------------------------------- /front/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /front/src/assets/avatar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyh-man/admin-vue-template/HEAD/front/src/assets/avatar.gif -------------------------------------------------------------------------------- /front/src/assets/login_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyh-man/admin-vue-template/HEAD/front/src/assets/login_bg.jpg -------------------------------------------------------------------------------- /front/src/http/http.js: -------------------------------------------------------------------------------- 1 | import * as login from './modules/login.js' 2 | import * as menu from './modules/menu.js' 3 | 4 | export default { 5 | login, 6 | menu 7 | } 8 | -------------------------------------------------------------------------------- /front/src/views/menu/Echarts.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /front/src/views/menu/Ueditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /front/src/views/dynamic/sys/UserList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /front/src/http/modules/login.js: -------------------------------------------------------------------------------- 1 | import http from '@/http/httpRequest.js' 2 | 3 | export function getToken() { 4 | return http({ 5 | url: '/auth/token', 6 | method: 'get' 7 | }) 8 | } -------------------------------------------------------------------------------- /front/src/http/modules/menu.js: -------------------------------------------------------------------------------- 1 | import http from '@/http/httpRequest.js' 2 | 3 | export function getMenus() { 4 | return http({ 5 | url: '/menu/getMenus', 6 | method: 'get' 7 | }) 8 | } -------------------------------------------------------------------------------- /front/src/views/dynamic/sys/MenuControl.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /front/src/views/dynamic/sys/RoleControl.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /front/src/views/menu/HomePage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/validator/group/AddGroup.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.validator.group; 2 | 3 | /** 4 | * 新增数据的 Group 校验规则 5 | */ 6 | public interface AddGroup { 7 | } 8 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/validator/group/UpdateGroup.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.validator.group; 2 | 3 | /** 4 | * 更新数据时的 Group 校验规则 5 | */ 6 | public interface UpdateGroup { 7 | } 8 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/validator/group/sys/LoginGroup.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.validator.group.sys; 2 | 3 | /** 4 | * 新增登录的 Group 校验规则 5 | */ 6 | public interface LoginGroup { 7 | } 8 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/validator/group/sys/RegisterGroup.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.validator.group.sys; 2 | 3 | /** 4 | * 新增注册的 Group 校验规则 5 | */ 6 | public interface RegisterGroup { 7 | } 8 | -------------------------------------------------------------------------------- /front/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import user from './module/user.js' 4 | import common from './module/common.js' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | modules: { 10 | user, 11 | common 12 | } 13 | }) -------------------------------------------------------------------------------- /front/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * URL地址 3 | * @param {*} s 4 | */ 5 | export function isURL (s) { 6 | return /^http[s]?:\/\/.*/.test(s) 7 | } 8 | 9 | /** 10 | * 判断是否为 动态路由 11 | * @param {*} s 12 | */ 13 | export function isDynamicRoutes(s) { 14 | return /DynamicRoutes/.test(s) 15 | } -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/mapper/xml/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/oss/mapper/xml/BackOssMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/mapper/xml/SysRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/mapper/xml/SysUserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/mapper/xml/SysUserRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /back/src/test/java/com/lyh/admin_template/back/BackApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BackApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /front/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lintOnSave: false, 3 | devServer: { 4 | port: process.env.NODE_ENV == "production" ? 8000 : 9000, 5 | proxy: { 6 | '/api': { 7 | target: process.env.VUE_APP_URL, 8 | // 允许跨域 9 | changeOrigin: true, 10 | ws: true, 11 | pathRewrite: { 12 | '^/api': '' 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sms/entity/SmsResponse.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sms.entity; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 用于接收并转换 sms 返回的数据 7 | */ 8 | @Data 9 | public class SmsResponse { 10 | private String Message; 11 | private String RequestId; 12 | private String Code; 13 | private String BizId; 14 | } 15 | -------------------------------------------------------------------------------- /back/src/main/resources/static/i18n/messages_zh_CN.properties: -------------------------------------------------------------------------------- 1 | test=测试 I18n 2 | user.name.empty=用户名不能为空 3 | user.mobile.empty=用户手机号不能为空 4 | user.password.empty=用户密码不能为空 5 | 6 | sys.user.name.notEmpty=用户名不能为空 7 | sys.user.phone.notEmpty=用户手机号不能为空 8 | sys.user.password.notEmpty=用户密码不能为空 9 | sys.user.code.notEmpty=验证码不能为空 10 | sys.user.phone.format.error=用户手机号格式错误 11 | sys.user.name.format.error=用户名格式错误 12 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.mapper; 2 | 3 | import com.lyh.admin_template.back.entity.User; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 用户表 Mapper 接口 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-06-08 13 | */ 14 | public interface UserMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.service; 2 | 3 | import com.lyh.admin_template.back.entity.User; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 用户表 服务类 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-06-08 13 | */ 14 | public interface UserService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/vo/JwtVo.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 保存 JWT 对应存储的数据 7 | */ 8 | @Data 9 | public class JwtVo { 10 | // 保存用户 ID 11 | private Long id; 12 | // 保存用户名 13 | private String name; 14 | // 保存用户手机号 15 | private String phone; 16 | // 保存 JWT 创建时间戳 17 | private Long time; 18 | } 19 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/BackApplication.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class BackApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(BackApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/back_oss.sql: -------------------------------------------------------------------------------- 1 | USE admin_template; 2 | 3 | -- 文件上传 4 | CREATE TABLE back_oss ( 5 | id bigint NOT NULL COMMENT '文件 ID', 6 | file_url varchar(500) COMMENT 'URL 地址', 7 | oss_name varchar(200) COMMENT '存储在 OSS 中的文件名', 8 | file_name varchar(100) COMMENT '文件名', 9 | create_time datetime COMMENT '创建时间', 10 | PRIMARY KEY (id), 11 | UNIQUE INDEX (oss_name) 12 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='文件上传'; 13 | -------------------------------------------------------------------------------- /front/src/http/auth.js: -------------------------------------------------------------------------------- 1 | // 引入 token,此处直接引入,也可以在 main.js 中全局引入 2 | import Cookies from 'js-cookie' 3 | 4 | // 设置 token 存储的 key 5 | const TokenKey = 'Admin-Token' 6 | 7 | // 获取 token 8 | export function getToken() { 9 | return Cookies.get(TokenKey) 10 | } 11 | 12 | // 设置 token 13 | export function setToken(token) { 14 | return Cookies.set(TokenKey, token) 15 | } 16 | 17 | // 移除 token 18 | export function removeToken() { 19 | return Cookies.remove(TokenKey) 20 | } -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/oss/mapper/BackOssMapper.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.oss.mapper; 2 | 3 | import com.lyh.admin_template.back.modules.oss.entity.BackOss; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 文件上传 Mapper 接口 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-06-19 13 | */ 14 | public interface BackOssMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/oss/service/BackOssService.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.oss.service; 2 | 3 | import com.lyh.admin_template.back.modules.oss.entity.BackOss; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 文件上传 服务类 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-06-19 13 | */ 14 | public interface BackOssService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/mapper/SysUserMapper.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.mapper; 2 | 3 | import com.lyh.admin_template.back.modules.sys.entity.SysUser; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 系统用户表 Mapper 接口 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-07-02 13 | */ 14 | public interface SysUserMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/mapper/SysRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.mapper; 2 | 3 | import com.lyh.admin_template.back.modules.sys.entity.SysRole; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 系统用户角色表 Mapper 接口 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-07-07 13 | */ 14 | public interface SysRoleMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/service/SysRoleService.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.service; 2 | 3 | import com.lyh.admin_template.back.modules.sys.entity.SysRole; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 系统用户角色表 服务类 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-07-07 13 | */ 14 | public interface SysRoleService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /front/src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | // 引入 VueI18n 3 | import VueI18n from 'vue-i18n' 4 | // 全局挂载 VueI18n 5 | Vue.use(VueI18n) 6 | 7 | // 创建 i18n 实例,并引入语言文件(可以是 js 文件、也可以为 json 文件) 8 | const i18n = new VueI18n({ 9 | // locale 为语言标识,通过切换locale的值来实现语言切换( this.$i18n.locale ) 10 | locale: 'zh', 11 | messages: { 12 | 'zh': require('@/i18n/languages/zh.json'), 13 | 'en': require('@/i18n/languages/en.json') 14 | } 15 | }) 16 | 17 | export default i18n -------------------------------------------------------------------------------- /front/src/mock/modules/login.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | // 登录 4 | export function getToken() { 5 | return { 6 | // isOpen: false, 7 | url: 'api/auth/token', 8 | type: 'get', 9 | data: { 10 | 'msg': 'success', 11 | 'code': 0, 12 | 'expire': Mock.Random.natural(60 * 60 * 1, 60 * 60 * 12), 13 | 'token': Mock.Random.string('abcdefghijklmnopqrstuvwxyz0123456789', 32) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/mapper/SysUserRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.mapper; 2 | 3 | import com.lyh.admin_template.back.modules.sys.entity.SysUserRole; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 系统用户角色表 Mapper 接口 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-07-07 13 | */ 14 | public interface SysUserRoleMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/service/SysUserRoleService.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.service; 2 | 3 | import com.lyh.admin_template.back.modules.sys.entity.SysUserRole; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 系统用户角色表 服务类 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-07-07 13 | */ 14 | public interface SysUserRoleService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /front/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/back_config.sql: -------------------------------------------------------------------------------- 1 | USE admin_template; 2 | 3 | -- 系统配置信息 4 | CREATE TABLE back_config ( 5 | id bigint NOT NULL COMMENT '配置信息 ID', 6 | param_key varchar(50) COMMENT 'key', 7 | param_value varchar(2000) COMMENT 'value', 8 | status tinyint DEFAULT 1 COMMENT '状态 0:隐藏 1:显示', 9 | remark varchar(500) COMMENT '备注', 10 | PRIMARY KEY (id), 11 | UNIQUE INDEX (param_key) 12 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系统配置信息表'; 13 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | /** 9 | *

10 | * 用户表 前端控制器 11 | *

12 | * 13 | * @author lyh 14 | * @since 2020-06-08 15 | */ 16 | @RestController 17 | @RequestMapping("/back/user") 18 | public class UserController { 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/service/SysUserService.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.lyh.admin_template.back.modules.sys.entity.SysUser; 5 | 6 | /** 7 | *

8 | * 系统用户表 服务类 9 | *

10 | * 11 | * @author lyh 12 | * @since 2020-07-02 13 | */ 14 | public interface SysUserService extends IService { 15 | public boolean saveUser(SysUser sysUser); 16 | } 17 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/controller/SysRoleController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | /** 9 | *

10 | * 系统用户角色表 前端控制器 11 | *

12 | * 13 | * @author lyh 14 | * @since 2020-07-07 15 | */ 16 | @RestController 17 | @RequestMapping("/sys/sys-role") 18 | public class SysRoleController { 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /back/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | 33 | .mvn 34 | .target 35 | mvnw 36 | mvnw.cmd 37 | */log 38 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/controller/SysUserRoleController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | /** 9 | *

10 | * 系统用户角色表 前端控制器 11 | *

12 | * 13 | * @author lyh 14 | * @since 2020-07-07 15 | */ 16 | @RestController 17 | @RequestMapping("/sys/sys-user-role") 18 | public class SysUserRoleController { 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /back/src/main/resources/static/i18n/messages_en_US.properties: -------------------------------------------------------------------------------- 1 | test=test I18n 2 | user.name.empty=User name cannot be null 3 | user.mobile.empty=User mobile cannot be null 4 | user.password.empty=User password cannot be null 5 | 6 | sys.user.name.notEmpty=Sys user name cannot be null 7 | sys.user.phone.notEmpty=Sys user mobile cannot be null 8 | sys.user.password.notEmpty=Sys user password cannot be null 9 | sys.user.code.notEmpty=Sys user code cannot be null 10 | sys.user.phone.format.error=Sys user mobile format error 11 | sys.user.name.format.error=Sys user name format error 12 | -------------------------------------------------------------------------------- /front/src/store/module/user.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // 开启命名空间(防止各模块间命名冲突),访问时需要使用 模块名 + 方法名 3 | namespaced: true, 4 | // 管理数据(状态) 5 | state: { 6 | // 用于保存用户名 7 | userName: 'Admin' 8 | }, 9 | // 更改 state(同步) 10 | mutations: { 11 | updateName(state, data) { 12 | if (data) { 13 | state.userName = data 14 | } else { 15 | state.userName = 'Admin' 16 | } 17 | } 18 | }, 19 | // 异步触发 mutations 20 | actions: { 21 | updateName({commit, state}, data) { 22 | commit("updateName", data) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.service.impl; 2 | 3 | import com.lyh.admin_template.back.entity.User; 4 | import com.lyh.admin_template.back.mapper.UserMapper; 5 | import com.lyh.admin_template.back.service.UserService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 用户表 服务实现类 12 | *

13 | * 14 | * @author lyh 15 | * @since 2020-06-08 16 | */ 17 | @Service 18 | public class UserServiceImpl extends ServiceImpl implements UserService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/vo/NamePwdLoginVo.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.vo; 2 | 3 | import com.lyh.admin_template.back.common.validator.group.sys.LoginGroup; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | 8 | /** 9 | * 登录时的视图数据类(view object), 10 | * 用于接收使用 用户名 + 密码 登陆的数据与操作。 11 | */ 12 | @Data 13 | public class NamePwdLoginVo { 14 | @NotEmpty(message = "{sys.user.name.notEmpty}", groups = {LoginGroup.class}) 15 | private String userName; 16 | @NotEmpty(message = "{sys.user.password.notEmpty}", groups = {LoginGroup.class}) 17 | private String password; 18 | } 19 | -------------------------------------------------------------------------------- /front/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /front/src/views/home/Content.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/back_user.sql: -------------------------------------------------------------------------------- 1 | -- DROP DATABASE IF EXISTS admin_template; 2 | -- 3 | -- CREATE DATABASE admin_template; 4 | 5 | USE admin_template; 6 | -- 用户表 7 | CREATE TABLE back_user ( 8 | id bigint NOT NULL COMMENT '用户 ID', 9 | name varchar(50) NOT NULL COMMENT '用户名', 10 | mobile varchar(20) NOT NULL COMMENT '用户手机号', 11 | password varchar(64) NOT NULL COMMENT '用户密码', 12 | create_time datetime COMMENT '创建时间', 13 | update_time datetime COMMENT '修改时间', 14 | delete_flag tinyint COMMENT '逻辑删除标志,0 表示未删除, 1 表示删除', 15 | version tinyint COMMENT '版本号', 16 | PRIMARY KEY(id), 17 | UNIQUE INDEX(name) 18 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='用户表'; 19 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/oss/service/impl/BackOssServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.oss.service.impl; 2 | 3 | import com.lyh.admin_template.back.modules.oss.entity.BackOss; 4 | import com.lyh.admin_template.back.modules.oss.mapper.BackOssMapper; 5 | import com.lyh.admin_template.back.modules.oss.service.BackOssService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 文件上传 服务实现类 12 | *

13 | * 14 | * @author lyh 15 | * @since 2020-06-19 16 | */ 17 | @Service 18 | public class BackOssServiceImpl extends ServiceImpl implements BackOssService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/service/impl/SysRoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.service.impl; 2 | 3 | import com.lyh.admin_template.back.modules.sys.entity.SysRole; 4 | import com.lyh.admin_template.back.modules.sys.mapper.SysRoleMapper; 5 | import com.lyh.admin_template.back.modules.sys.service.SysRoleService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 系统用户角色表 服务实现类 12 | *

13 | * 14 | * @author lyh 15 | * @since 2020-07-07 16 | */ 17 | @Service 18 | public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /front/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import http from '@/http/http.js' 6 | import i18n from '@/i18n/index.js' 7 | // 引入 element-ui 8 | import ElementUI from 'element-ui' 9 | // 引入 element-ui 的 css 文件 10 | import 'element-ui/lib/theme-chalk/index.css'; 11 | // 声明使用 element-ui 12 | Vue.use(ElementUI); 13 | 14 | // 全局挂载 http(axios),使用的时候直接使用 this.$http 即可。 15 | Vue.prototype.$http=http 16 | 17 | // 非生产环境, 适配mockjs模拟数据 18 | if (process.env.NODE_ENV !== 'production') { 19 | require('@/mock') 20 | } 21 | 22 | Vue.config.productionTip = false 23 | 24 | new Vue({ 25 | router, 26 | store, 27 | i18n, 28 | render: h => h(App) 29 | }).$mount('#app') 30 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/service/impl/SysUserRoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.service.impl; 2 | 3 | import com.lyh.admin_template.back.modules.sys.entity.SysUserRole; 4 | import com.lyh.admin_template.back.modules.sys.mapper.SysUserRoleMapper; 5 | import com.lyh.admin_template.back.modules.sys.service.SysUserRoleService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 系统用户角色表 服务实现类 12 | *

13 | * 14 | * @author lyh 15 | * @since 2020-07-07 16 | */ 17 | @Service 18 | public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/vo/PhoneCodeLoginVo.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.vo; 2 | 3 | import com.lyh.admin_template.back.common.validator.group.sys.LoginGroup; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.Pattern; 8 | 9 | /** 10 | * 登录时的视图数据类(view object), 11 | * 用于接收使用 手机号 + 验证码 登陆的数据与操作。 12 | */ 13 | @Data 14 | public class PhoneCodeLoginVo { 15 | @NotEmpty(message = "{sys.user.phone.notEmpty}", groups = {LoginGroup.class}) 16 | @Pattern(message = "{sys.user.phone.format.error}", regexp = "0?(13|14|15|18|17)[0-9]{9}", groups = {LoginGroup.class}) 17 | private String phone; 18 | @NotEmpty(message = "{sys.user.code.notEmpty}", groups = {LoginGroup.class}) 19 | private String code; 20 | } 21 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/vo/PhonePwdLoginVo.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.vo; 2 | 3 | import com.lyh.admin_template.back.common.validator.group.sys.LoginGroup; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.Pattern; 8 | 9 | /** 10 | * 登录时的视图数据类(view object), 11 | * 用于接收使用 手机号 + 密码 登陆的数据与操作。 12 | */ 13 | @Data 14 | public class PhonePwdLoginVo { 15 | @NotEmpty(message = "{sys.user.phone.notEmpty}", groups = {LoginGroup.class}) 16 | @Pattern(message = "{sys.user.phone.format.error}", regexp = "0?(13|14|15|18|17)[0-9]{9}", groups = {LoginGroup.class}) 17 | private String phone; 18 | @NotEmpty(message = "{sys.user.password.notEmpty}", groups = {LoginGroup.class}) 19 | private String password; 20 | } 21 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/vo/MailVo.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.vo; 2 | 3 | import lombok.Data; 4 | import org.springframework.web.multipart.MultipartFile; 5 | 6 | @Data 7 | public class MailVo { 8 | 9 | /** 10 | * 邮件发送人 11 | */ 12 | private String from; 13 | /** 14 | * 邮件接收人 15 | */ 16 | private String[] to; 17 | /** 18 | * 邮件抄送 19 | */ 20 | private String[] cc; 21 | /** 22 | * 邮件密送 23 | */ 24 | private String[] bcc; 25 | /** 26 | * 邮件主题 27 | */ 28 | private String subject; 29 | /** 30 | * 邮件内容 31 | */ 32 | private String text; 33 | /** 34 | * 邮件附件 35 | */ 36 | private MultipartFile[] files; 37 | /** 38 | * 邮件附件在服务器存储的地址 39 | */ 40 | private String[] fileUrls; 41 | } 42 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.IOException; 6 | import java.io.PrintWriter; 7 | import java.io.StringWriter; 8 | 9 | /** 10 | * 异常处理工具类,将异常堆栈信息转为 String 类型 11 | */ 12 | @Slf4j 13 | public class ExceptionUtil { 14 | public static String getMessage(Exception e) { 15 | String message = null; 16 | try(StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { 17 | e.printStackTrace(pw); 18 | pw.flush(); 19 | sw.flush(); 20 | message = sw.toString(); 21 | }catch (IOException io) { 22 | io.printStackTrace(); 23 | log.error(io.getMessage()); 24 | } 25 | return message; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /front/src/views/dynamic/DynamicMenu.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | 26 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/GsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import com.google.gson.Gson; 4 | 5 | /** 6 | * Gson 工具类,用于 Object 与 Json 字符串形式互转 7 | */ 8 | public class GsonUtil { 9 | private final static Gson GSON = new Gson(); 10 | 11 | /** 12 | * Object 转 String 数据(JSON 字符串) 13 | */ 14 | public static String toJson(Object object) { 15 | if (object instanceof Integer || object instanceof Short || object instanceof Byte 16 | || object instanceof Long || object instanceof Character || object instanceof Boolean 17 | || object instanceof Double || object instanceof String || object instanceof Float) { 18 | return String.valueOf(object); 19 | } 20 | return GSON.toJson(object); 21 | } 22 | 23 | /** 24 | * string(Json 字符串) 转 Object。 25 | */ 26 | public static T fromJson(String json, Class tClass) { 27 | return GSON.fromJson(json, tClass); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /front/src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import * as login from './modules/login.js' 3 | import * as menu from './modules/menu.js' 4 | 5 | // 可以通过 isOpen 参数设置是否拦截整个模块的 mock 功能 6 | fnCreate(login, true) 7 | fnCreate(menu, true) 8 | 9 | /** 10 | * 创建mock模拟数据 11 | * @param {*} mod 模块 12 | * @param {*} isOpen 是否开启? 13 | */ 14 | function fnCreate(mod, isOpen = true) { 15 | if (isOpen) { 16 | for (var key in mod) { 17 | ((res) => { 18 | if (res.isOpen !== false) { 19 | Mock.mock(new RegExp(res.url), res.type, (opts) => { 20 | opts['data'] = opts.body ? JSON.parse(opts.body) : null 21 | delete opts.body 22 | console.log('\n') 23 | console.log('%cmock拦截, 请求: ', 'color:blue', opts) 24 | console.log('%cmock拦截, 响应: ', 'color:blue', res.data) 25 | return res.data 26 | }) 27 | } 28 | })(mod[key]() || {}) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /front/src/i18n/languages/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": { 3 | "title": "管理员登录", 4 | "userName": "用户名", 5 | "password": "密码", 6 | "language": "语言选择", 7 | "signIn": "登录", 8 | "userNameNotNull": "用户名不能为空", 9 | "passwordNotNull": "密码不能为空", 10 | "signInSuccess": "登录成功" 11 | }, 12 | "language": { 13 | "setting": "设置", 14 | "languageSettings": "语言设置: ", 15 | "zh": "中文", 16 | "en": "英语" 17 | }, 18 | "header": { 19 | "foldAside": "折叠侧边栏", 20 | "unFoldAside": "展开侧边栏", 21 | "setUp": "设置", 22 | "help": "帮助", 23 | "blogAddress": "博客地址", 24 | "codeAddress": "代码地址", 25 | "userSetUp": "用户设置", 26 | "updatePassword": "修改密码", 27 | "logOut": "退出" 28 | }, 29 | "aside": { 30 | "adminCenter": "后台管理中心", 31 | "admin": "后台", 32 | "homePage": "首页" 33 | }, 34 | "tab": { 35 | "closeCurrentTabs": "关闭当前标签页", 36 | "closeOtherTabs": "关闭其它标签页", 37 | "closeAllTabs": "关闭全部标签页", 38 | "refreshCurrentTabs": "刷新当前标签页" 39 | } 40 | } -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/MessageSourceUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.MessageSource; 5 | import org.springframework.context.i18n.LocaleContextHolder; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.Locale; 9 | 10 | /** 11 | * 国际化工具类,用于获取国际化文件中的数据 12 | */ 13 | @Component 14 | public class MessageSourceUtil { 15 | 16 | @Autowired 17 | private MessageSource messageSource; 18 | 19 | public String getMessage(String code) { 20 | return getMessage(code, null); 21 | } 22 | 23 | public String getMessage(String code, Object[] args) { 24 | return getMessage(code, args, ""); 25 | } 26 | 27 | public String getMessage(String code, Object[] args, String defaultMsg) { 28 | // 获取当前语言信息 29 | Locale locale = LocaleContextHolder.getLocale(); 30 | // 根据 code 从国际化资源文件中查找相关信息 31 | return messageSource.getMessage(code, args, defaultMsg, locale); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/vo/RegisterVo.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.vo; 2 | 3 | import com.lyh.admin_template.back.common.validator.group.sys.RegisterGroup; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.Pattern; 8 | 9 | /** 10 | * 注册时对应的视图数据类(view object), 11 | * 用于接收并处理 注册时的数据。 12 | */ 13 | @Data 14 | public class RegisterVo { 15 | @NotEmpty(message = "{sys.user.name.notEmpty}", groups = {RegisterGroup.class}) 16 | @Pattern(message = "{sys.user.name.format.error}", regexp = "^.*[^\\d].*$", groups = {RegisterGroup.class}) 17 | private String userName; 18 | @NotEmpty(message = "{sys.user.password.notEmpty}", groups = {RegisterGroup.class}) 19 | private String password; 20 | @NotEmpty(message = "{sys.user.phone.notEmpty}", groups = {RegisterGroup.class}) 21 | @Pattern(message = "{sys.user.phone.format.error}", regexp = "0?(13|14|15|18|17)[0-9]{9}", groups = {RegisterGroup.class}) 22 | private String phone; 23 | @NotEmpty(message = "{sys.user.code.notEmpty}", groups = {RegisterGroup.class}) 24 | private String code; 25 | } 26 | -------------------------------------------------------------------------------- /front/src/components/common/404.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/MD5Util.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | /** 7 | * 定义一个 MD5 工具类,用于密码加密 8 | */ 9 | public class MD5Util { 10 | public static String encrypt(String strSrc) { 11 | try { 12 | char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', 13 | '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 14 | byte[] bytes = strSrc.getBytes(); 15 | MessageDigest md = MessageDigest.getInstance("MD5"); 16 | md.update(bytes); 17 | bytes = md.digest(); 18 | int j = bytes.length; 19 | char[] chars = new char[j * 2]; 20 | int k = 0; 21 | for (int i = 0; i < bytes.length; i++) { 22 | byte b = bytes[i]; 23 | chars[k++] = hexChars[b >>> 4 & 0xf]; 24 | chars[k++] = hexChars[b & 0xf]; 25 | } 26 | return new String(chars); 27 | } catch (NoSuchAlgorithmException e) { 28 | throw new RuntimeException("MD5加密出错!!+" + e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /front/src/i18n/languages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": { 3 | "title": "Administrator Login", 4 | "userName": "UserName", 5 | "password": "Password", 6 | "language": "Language", 7 | "signIn": "SignIn", 8 | "userNameNotNull": "UserName Not Null", 9 | "passwordNotNull": "Password Not Null", 10 | "signInSuccess": "SignIn Success" 11 | }, 12 | "language": { 13 | "setting": "Settings", 14 | "languageSettings": "Language Settings: ", 15 | "zh": "Chinese", 16 | "en": "English" 17 | }, 18 | "header": { 19 | "foldAside": "Fold Aside", 20 | "unFoldAside": "Un Fold Aside", 21 | "setUp": "SetUp", 22 | "help": "Help", 23 | "blogAddress": "Blog Address", 24 | "codeAddress": "Code Address", 25 | "userSetUp": "User SetUp", 26 | "updatePassword": "Update Password", 27 | "logOut": "LogOut" 28 | }, 29 | "aside": { 30 | "adminCenter": "Admin Center", 31 | "admin": "AC", 32 | "homePage": "Home Page" 33 | }, 34 | "tab": { 35 | "closeCurrentTabs": "Close Current Tabs", 36 | "closeOtherTabs": "Close Other Tabs", 37 | "closeAllTabs": "Close All Tabs", 38 | "refreshCurrentTabs": "Refresh Current Tabs" 39 | } 40 | } -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/handler/MyMetaObjectHandler.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.handler; 2 | 3 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 4 | import org.apache.ibatis.reflection.MetaObject; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * 自动填充处理类, 11 | * insertFill() 表示插入时的填充规则, 12 | * updateFill() 表示更新时的填充规则。 13 | */ 14 | @Component 15 | public class MyMetaObjectHandler implements MetaObjectHandler { 16 | 17 | /** 18 | * 插入时的填充规则 19 | * @param metaObject 20 | */ 21 | @Override 22 | public void insertFill(MetaObject metaObject) { 23 | this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); 24 | this.strictInsertFill(metaObject, "updateTime", Date.class, new Date()); 25 | this.strictInsertFill(metaObject, "deleteFlag", Integer.class, 0); 26 | this.strictInsertFill(metaObject, "disabledFlag", Integer.class, 0); 27 | this.strictInsertFill(metaObject, "version", Integer.class, 1); 28 | } 29 | 30 | /** 31 | * 更新时的填充规则 32 | * @param metaObject 33 | */ 34 | @Override 35 | public void updateFill(MetaObject metaObject) { 36 | this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.19.2", 12 | "core-js": "^3.6.4", 13 | "element-ui": "^2.13.2", 14 | "js-cookie": "^2.2.1", 15 | "mockjs": "^1.1.0", 16 | "vue": "^2.6.11", 17 | "vue-i18n": "^8.17.7", 18 | "vue-router": "^3.1.6", 19 | "vuex": "^3.1.3" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^4.3.0", 23 | "@vue/cli-plugin-eslint": "^4.3.0", 24 | "@vue/cli-plugin-router": "^4.3.0", 25 | "@vue/cli-plugin-vuex": "^4.3.0", 26 | "@vue/cli-service": "^4.3.0", 27 | "babel-eslint": "^10.1.0", 28 | "eslint": "^6.7.2", 29 | "eslint-plugin-vue": "^6.2.2", 30 | "vue-template-compiler": "^2.6.11" 31 | }, 32 | "eslintConfig": { 33 | "root": true, 34 | "env": { 35 | "node": true 36 | }, 37 | "extends": [ 38 | "plugin:vue/essential", 39 | "eslint:recommended" 40 | ], 41 | "parserOptions": { 42 | "parser": "babel-eslint" 43 | }, 44 | "rules": {} 45 | }, 46 | "browserslist": [ 47 | "> 1%", 48 | "last 2 versions", 49 | "not dead" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/sys/sys_role.sql: -------------------------------------------------------------------------------- 1 | -- DROP DATABASE IF EXISTS admin_template; 2 | -- 3 | -- CREATE DATABASE admin_template; 4 | 5 | -- --------------------------sys_role 角色表--------------------------------------- 6 | USE admin_template; 7 | DROP TABLE IF EXISTS sys_role; 8 | -- 系统用户角色表 9 | CREATE TABLE sys_role ( 10 | id bigint NOT NULL COMMENT '角色 ID', 11 | role_name varchar(20) NOT NULL COMMENT '角色名称', 12 | role_code varchar(20) DEFAULT NULL COMMENT '角色码', 13 | remark varchar(255) DEFAULT NULL COMMENT '角色备注', 14 | create_time datetime DEFAULT NULL COMMENT '创建时间', 15 | update_time datetime DEFAULT NULL COMMENT '修改时间', 16 | delete_flag tinyint DEFAULT NULL COMMENT '逻辑删除标志,0 表示未删除, 1 表示删除', 17 | PRIMARY KEY(id) 18 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系统用户角色表'; 19 | 20 | 21 | -- 插入数据 22 | INSERT INTO `sys_role`(`id`, `role_name`, `role_code`, `remark`, `create_time`, `update_time`, `delete_flag`) 23 | VALUES (1278601251755451245, 'superAdmin', '1001', '超级管理员','2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 24 | (1278601251755452551, 'admin', '2001', '普通管理员','2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 25 | (1278601251755458779, 'user', '3001', '普通用户','2020-07-02 16:07:48', '2020-07-02 16:07:48', 0); 26 | 27 | -- --------------------------sys_role 角色表--------------------------------------- 28 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/sys/sys_user_role.sql: -------------------------------------------------------------------------------- 1 | -- DROP DATABASE IF EXISTS admin_template; 2 | -- 3 | -- CREATE DATABASE admin_template; 4 | 5 | -- --------------------------sys_user_role 用户角色表--------------------------------------- 6 | USE admin_template; 7 | DROP TABLE IF EXISTS sys_user_role; 8 | -- 系统用户角色表 9 | CREATE TABLE sys_user_role ( 10 | id bigint NOT NULL COMMENT '用户角色表 ID', 11 | role_id bigint NOT NULL COMMENT '角色 ID', 12 | user_id bigint NOT NULL COMMENT '用户 ID', 13 | create_time datetime DEFAULT NULL COMMENT '创建时间', 14 | update_time datetime DEFAULT NULL COMMENT '修改时间', 15 | delete_flag tinyint DEFAULT NULL COMMENT '逻辑删除标志,0 表示未删除, 1 表示删除', 16 | PRIMARY KEY(id) 17 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系统用户角色表'; 18 | 19 | 20 | -- 插入数据 21 | INSERT INTO `sys_user_role`(`id`, `role_id`, `user_id`, `create_time`, `update_time`, `delete_flag`) 22 | VALUES (1278601251755452234, '1278601251755451245', '1278601251755454466', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 23 | (1278601251755453544, '1278601251755452551', '1278601251755451232', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 24 | (1278601251755454664, '1278601251755458779', '1278601251755456778', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0); 25 | 26 | -- --------------------------sys_user_role 用户角色表--------------------------------------- 27 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/controller/test/TestJWTController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.controller.test; 2 | 3 | import com.lyh.admin_template.back.common.utils.JwtUtil; 4 | import com.lyh.admin_template.back.common.utils.Result; 5 | import com.lyh.admin_template.back.modules.sys.entity.SysUser; 6 | import io.jsonwebtoken.Claims; 7 | import io.swagger.annotations.Api; 8 | import io.swagger.annotations.ApiOperation; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RequestMapping("/test/jwt") 14 | @RestController 15 | @Api(tags = "测试 JWT") 16 | public class TestJWTController { 17 | 18 | @ApiOperation(value = "获取 token") 19 | @PostMapping("/getToken") 20 | public Result testJwt() { 21 | return Result.ok().data("token", JwtUtil.getJwtToken("tom")); 22 | } 23 | 24 | @ApiOperation(value = "测试是否过期") 25 | @PostMapping("/testExpire") 26 | public Result testJwtExpire(String jwtToken) { 27 | if (JwtUtil.checkToken(jwtToken)) { 28 | Claims claims = JwtUtil.getTokenBody(jwtToken); 29 | return Result.ok().message("token 未过期").data("claims", claims); 30 | } 31 | return Result.ok().message("token 已过期"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /front/README.md: -------------------------------------------------------------------------------- 1 | # front 2 | 3 | ## 参考博文地址(手把手教你从零构建一个后台管理系统模板) 4 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(一):搭建基本环境](https://www.cnblogs.com/l-y-h/p/12930895.html) 5 | 6 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(二):引入 element-ui 定义基本页面显示](https://www.cnblogs.com/l-y-h/p/12935300.html) 7 | 8 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(三):引入 js-cookie、axios、mock 封装请求处理以及返回结果](https://www.cnblogs.com/l-y-h/p/12955001.html) 9 | 10 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(四):引入 vuex 进行状态管理、引入 vue-i18n 进行国际化管理](https://www.cnblogs.com/l-y-h/p/12963576.html) 11 | 12 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(五):引入 vue-router 进行路由管理、模块化封装 axios 请求、使用 iframe 标签嵌套页面](https://www.cnblogs.com/l-y-h/p/12973364.html) 13 | 14 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(六):使用 vue-router 进行动态加载菜单](https://www.cnblogs.com/l-y-h/p/13052196.html) 15 | 16 | ## 代码地址 17 | [admin-vue-template](https://github.com/lyh-man/admin-vue-template.git) 18 | 19 | ## Project setup 20 | ``` 21 | npm install 22 | ``` 23 | 24 | ### Compiles and hot-reloads for development 25 | ``` 26 | npm run serve 27 | ``` 28 | 29 | ### Compiles and minifies for production 30 | ``` 31 | npm run build 32 | ``` 33 | 34 | ### Lints and fixes files 35 | ``` 36 | npm run lint 37 | ``` 38 | 39 | ### Customize configuration 40 | See [Configuration Reference](https://cli.vuejs.org/config/). 41 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sms/controller/TestSMSController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sms.controller; 2 | 3 | import com.lyh.admin_template.back.common.utils.Result; 4 | import com.lyh.admin_template.back.common.utils.SmsUtil; 5 | import io.swagger.annotations.Api; 6 | import io.swagger.annotations.ApiImplicitParam; 7 | import io.swagger.annotations.ApiOperation; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | @RestController 16 | @RequestMapping("/sms") 17 | @Api(tags = "短信发送") 18 | public class TestSMSController { 19 | 20 | @Autowired 21 | private SmsUtil smsUtil; 22 | 23 | @ApiOperation(value = "测试短信发送功能") 24 | @ApiImplicitParam(name = "phoneNumber", required = true, value = "手机号", paramType = "query", dataType = "String") 25 | @PostMapping("/testSend") 26 | public Result testSend(@RequestParam String phoneNumber) { 27 | if (StringUtils.isNotEmpty(smsUtil.sendSms(phoneNumber))) { 28 | return Result.ok().message("短信发送成功"); 29 | } 30 | return Result.error().message("短信发送失败"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /front/src/views/home/Setup.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 60 | 61 | 63 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/entity/SysUserRole.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | 5 | import java.util.Date; 6 | 7 | import java.io.Serializable; 8 | import io.swagger.annotations.ApiModel; 9 | import io.swagger.annotations.ApiModelProperty; 10 | import lombok.Data; 11 | import lombok.EqualsAndHashCode; 12 | import lombok.experimental.Accessors; 13 | 14 | /** 15 | *

16 | * 系统用户角色表 17 | *

18 | * 19 | * @author lyh 20 | * @since 2020-07-07 21 | */ 22 | @Data 23 | @EqualsAndHashCode(callSuper = false) 24 | @Accessors(chain = true) 25 | @ApiModel(value="SysUserRole对象", description="系统用户角色表") 26 | public class SysUserRole implements Serializable { 27 | 28 | private static final long serialVersionUID=1L; 29 | 30 | @ApiModelProperty(value = "用户角色表 ID") 31 | @TableId(value = "id", type = IdType.ASSIGN_ID) 32 | private Long id; 33 | 34 | @ApiModelProperty(value = "角色 ID") 35 | private Long roleId; 36 | 37 | @ApiModelProperty(value = "用户 ID") 38 | private Long userId; 39 | 40 | @TableField(fill = FieldFill.INSERT) 41 | @ApiModelProperty(value = "创建时间") 42 | private Date createTime; 43 | 44 | @TableField(fill = FieldFill.INSERT_UPDATE) 45 | @ApiModelProperty(value = "修改时间") 46 | private Date updateTime; 47 | 48 | @TableField(fill = FieldFill.INSERT) 49 | @TableLogic(value = "0", delval = "1") 50 | @ApiModelProperty(value = "逻辑删除标志,0 表示未删除, 1 表示删除") 51 | private Integer deleteFlag; 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/config/MyBatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.config; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor; 4 | import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; 5 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 6 | import org.mybatis.spring.annotation.MapperScan; 7 | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * 自定义一个 MybatisPlus 配置类,配置分页插件、乐观锁插件 13 | * mapper 扫描也可在此写上 14 | */ 15 | @Configuration 16 | @MapperScan(basePackages = {"com.lyh.admin_template.back.mapper", "com.lyh.admin_template.back.modules.**.mapper"}) 17 | public class MyBatisPlusConfig { 18 | /** 19 | * 分页插件 20 | * @return 分页插件的实例 21 | */ 22 | @Bean 23 | public PaginationInterceptor paginationInterceptor() { 24 | return new PaginationInterceptor(); 25 | } 26 | 27 | /** 28 | * 乐观锁插件 29 | * @return 乐观锁插件的实例 30 | */ 31 | @Bean 32 | public OptimisticLockerInterceptor optimisticLockerInterceptor() { 33 | return new OptimisticLockerInterceptor(); 34 | } 35 | 36 | @Bean 37 | public Jackson2ObjectMapperBuilderCustomizer builderCustomizer() { 38 | return builder -> { 39 | // 所有 Long 类型转换成 String 到前台 40 | builder.serializerByType(Long.class, ToStringSerializer.instance); 41 | }; 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/entity/SysRole.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | 5 | import java.util.Date; 6 | 7 | import java.io.Serializable; 8 | 9 | import io.swagger.annotations.ApiModel; 10 | import io.swagger.annotations.ApiModelProperty; 11 | import lombok.Data; 12 | import lombok.EqualsAndHashCode; 13 | import lombok.experimental.Accessors; 14 | 15 | /** 16 | *

17 | * 系统用户角色表 18 | *

19 | * 20 | * @author lyh 21 | * @since 2020-07-07 22 | */ 23 | @Data 24 | @EqualsAndHashCode(callSuper = false) 25 | @Accessors(chain = true) 26 | @ApiModel(value="SysRole对象", description="系统用户角色表") 27 | public class SysRole implements Serializable { 28 | 29 | private static final long serialVersionUID=1L; 30 | 31 | @ApiModelProperty(value = "角色 ID") 32 | @TableId(value = "id", type = IdType.ASSIGN_ID) 33 | private Long id; 34 | 35 | @ApiModelProperty(value = "角色名称") 36 | private String roleName; 37 | 38 | @ApiModelProperty(value = "角色码") 39 | private String roleCode; 40 | 41 | @ApiModelProperty(value = "角色备注") 42 | private String remark; 43 | 44 | @TableField(fill = FieldFill.INSERT) 45 | @ApiModelProperty(value = "创建时间") 46 | private Date createTime; 47 | 48 | @TableField(fill = FieldFill.INSERT_UPDATE) 49 | @ApiModelProperty(value = "修改时间") 50 | private Date updateTime; 51 | 52 | @TableField(fill = FieldFill.INSERT) 53 | @TableLogic(value = "0", delval = "1") 54 | @ApiModelProperty(value = "逻辑删除标志,0 表示未删除, 1 表示删除") 55 | private Integer deleteFlag; 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /front/src/http/httpRequest.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { 3 | getToken, 4 | removeToken 5 | } from '@/http/auth.js' 6 | import router from '@/router' 7 | import { 8 | Message 9 | } from 'element-ui' 10 | import axios from 'axios' 11 | 12 | // 创建 axios 实例 13 | const http = axios.create({ 14 | // 统一 url 配置,定义访问前缀 baseURL 15 | baseURL: '/api', 16 | // 定义请求超时时间 17 | timeout: 10000, 18 | // 请求带上 cookie 19 | withCredentials: true, 20 | // 定义消息头 21 | headers: { 22 | 'Content-Type': 'application/json; charset=utf-8' 23 | } 24 | }) 25 | 26 | // 定义请求拦截器 27 | http.interceptors.request.use( 28 | config => { 29 | // 让每个请求携带 token 30 | config.headers['Admin-Token'] = getToken() 31 | return config 32 | }, 33 | error => { 34 | Promise.reject(error) 35 | } 36 | ) 37 | 38 | // 定义响应拦截器 39 | http.interceptors.response.use( 40 | response => { 41 | const res = response.data 42 | // 当 token 失效时,清除 cookie 保存的 token 值,并跳转到登陆界面 43 | if (res && res.code === 401) { 44 | removeToken() 45 | Message({ 46 | message: res.message, 47 | type: 'error', 48 | duration: 5000 49 | }) 50 | router.push({ 51 | name: 'Login' 52 | }) 53 | } 54 | // 未找到页面时,跳转到 404 页面 55 | if (res && res.code === 404) { 56 | router.push({ 57 | name: '404' 58 | }) 59 | } 60 | return response 61 | }, 62 | error => { 63 | return Promise.reject(error) 64 | } 65 | ) 66 | 67 | export default http -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/exception/GlobalException.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.exception; 2 | 3 | import lombok.Data; 4 | import org.apache.http.HttpStatus; 5 | 6 | /** 7 | * 自定义异常, 8 | * 可以自定义 异常信息 message 以及 响应状态码 code(默认为 500)。 9 | * 10 | * 依赖信息说明: 11 | * 此处使用 @Data 注解,需导入 lombok 相关依赖文件。 12 | * 使用 HttpStatus 的常量表示 响应状态码,需导入 httpcore 相关依赖文件。 13 | */ 14 | @Data 15 | public class GlobalException extends RuntimeException { 16 | /** 17 | * 保存异常信息 18 | */ 19 | private String message; 20 | 21 | /** 22 | * 保存响应状态码 23 | */ 24 | private Integer code = HttpStatus.SC_INTERNAL_SERVER_ERROR; 25 | 26 | /** 27 | * 默认构造方法,根据异常信息 构建一个异常实例对象 28 | * @param message 异常信息 29 | */ 30 | public GlobalException(String message) { 31 | super(message); 32 | this.message = message; 33 | } 34 | 35 | /** 36 | * 根据异常信息、响应状态码构建 一个异常实例对象 37 | * @param message 异常信息 38 | * @param code 响应状态码 39 | */ 40 | public GlobalException(String message, Integer code) { 41 | super(message); 42 | this.message = message; 43 | this.code = code; 44 | } 45 | 46 | /** 47 | * 根据异常信息,异常对象构建 一个异常实例对象 48 | * @param message 异常信息 49 | * @param e 异常对象 50 | */ 51 | public GlobalException(String message, Throwable e) { 52 | super(message, e); 53 | this.message = message; 54 | } 55 | 56 | /** 57 | * 根据异常信息,响应状态码,异常对象构建 一个异常实例对象 58 | * @param message 异常信息 59 | * @param code 响应状态码 60 | * @param e 异常对象 61 | */ 62 | public GlobalException(String message, Integer code, Throwable e) { 63 | super(message, e); 64 | this.message = message; 65 | this.code = code; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/oss/entity/BackOss.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.oss.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.FieldFill; 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableId; 7 | import com.fasterxml.jackson.annotation.JsonFormat; 8 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 9 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 10 | import io.swagger.annotations.ApiModel; 11 | import io.swagger.annotations.ApiModelProperty; 12 | import lombok.Data; 13 | import lombok.EqualsAndHashCode; 14 | import lombok.experimental.Accessors; 15 | 16 | import java.io.Serializable; 17 | import java.util.Date; 18 | 19 | /** 20 | *

21 | * 文件上传 22 | *

23 | * 24 | * @author lyh 25 | * @since 2020-06-19 26 | */ 27 | @Data 28 | @EqualsAndHashCode(callSuper = false) 29 | @Accessors(chain = true) 30 | @ApiModel(value="BackOss对象", description="文件上传") 31 | public class BackOss implements Serializable { 32 | 33 | private static final long serialVersionUID=1L; 34 | 35 | @ApiModelProperty(value = "文件 ID") 36 | @TableId(value = "id", type = IdType.ASSIGN_ID) 37 | // @JsonSerialize(using = ToStringSerializer.class) 38 | private Long id; 39 | 40 | @ApiModelProperty(value = "URL 地址") 41 | private String fileUrl; 42 | 43 | @ApiModelProperty(value = "存储在 OSS 中的文件名") 44 | private String ossName; 45 | 46 | @ApiModelProperty(value = "文件名") 47 | private String fileName; 48 | 49 | @TableField(fill = FieldFill.INSERT) 50 | @ApiModelProperty(value = "创建时间") 51 | // @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8") 52 | private Date createTime; 53 | } 54 | -------------------------------------------------------------------------------- /back/README.md: -------------------------------------------------------------------------------- 1 | 项目说明: 2 | + 此项目为 后台管理项目模板 -- 后台代码 3 | 4 | 项目特点: 5 | + 引入 Swagger 生成接口文档 6 | + 引入 MyBatis-plus 进行持久层操作 7 | + 引入 JSR 303 进行数据校验 8 | + 自定义国际化处理操作 9 | 10 | 11 | 博客地址: 12 | 13 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(一): 搭建基本环境、整合 Swagger、MyBatisPlus、JSR303 以及国际化操作](https://www.cnblogs.com/l-y-h/p/13083375.html) 14 | 15 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(二): 整合 Redis(常用工具类、缓存)、整合邮件发送功能](https://www.cnblogs.com/l-y-h/p/13163653.html) 16 | 17 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片](https://www.cnblogs.com/l-y-h/p/13202746.html) 18 | 19 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(四): 整合阿里云 短信服务、整合 JWT 单点登录](https://www.cnblogs.com/l-y-h/p/13214493.html) 20 | 21 | [SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(五): 数据表设计、使用 jwt、redis、sms 工具类完善注册登录逻辑](https://www.cnblogs.com/l-y-h/p/13264307.html) 22 | 23 | 代码结构: 24 | ```javascript 25 | back 26 | |--- src 27 | | |--- main 保存源代码 28 | | | |--- java 代码目录 29 | | | | |--- common 保存公共操作 30 | | | | | |--- config 保存配置类 31 | | | | | |--- exception 保存异常处理操作 32 | | | | | |--- utils 保存工具类 33 | | | | | |--- validator 保存 JSR303 校验相关操作 34 | | | | |--- controller 保存控制层代码 35 | | | | |--- entity 保存实体类代码 36 | | | | |--- handler 保存数据处理相关操作 37 | | | | |--- mapper 保存 sql 相关映射操作 38 | | | | |--- service 保存业务层代码 39 | | 40 | | | |--- resources 资源目录 41 | | | | |--- static 用于保存项目静态文件 42 | | | | |--- application.yml 用于保存项目的配置信息 43 | | | | |--- logback-spring.xml 用于保存日志的配置信息 44 | | 45 | | |--- test 保存测试代码 46 | | | |--- java 47 | | 48 | |--- pom.xml 用于保存项目依赖信息 49 | ``` 50 | -------------------------------------------------------------------------------- /front/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 56 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/controller/test/TestMailController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.controller.test; 2 | 3 | import com.lyh.admin_template.back.common.utils.GsonUtil; 4 | import com.lyh.admin_template.back.common.utils.MailUtil; 5 | import com.lyh.admin_template.back.common.utils.Result; 6 | import com.lyh.admin_template.back.vo.MailVo; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import org.springframework.web.bind.annotation.RestController; 12 | import org.springframework.web.multipart.MultipartFile; 13 | import org.springframework.web.multipart.MultipartHttpServletRequest; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.util.List; 17 | 18 | /** 19 | * 测试邮件发送功能 20 | */ 21 | @RestController 22 | @RequestMapping("/test/mail") 23 | public class TestMailController { 24 | 25 | @Autowired 26 | private MailUtil mailUtil; 27 | 28 | /** 29 | * 获取邮件方式一,使用 HttpServletRequest 获取,并手动解析数据 30 | */ 31 | @PostMapping("/send") 32 | public Result send(HttpServletRequest request) { 33 | MultipartHttpServletRequest params = (MultipartHttpServletRequest) request; 34 | List files = params.getFiles("files"); 35 | MailVo mailVoReal = GsonUtil.fromJson(params.getParameter("mailVo"), MailVo.class); 36 | mailVoReal.setFiles(files.toArray(new MultipartFile[]{})); 37 | mailUtil.sendMail(mailVoReal); 38 | return Result.ok(); 39 | } 40 | 41 | /** 42 | * 获取邮件方式二,使用 @RequestParam 获取 json 字符串(使用 Gson 手动转换为 对象) 43 | */ 44 | @PostMapping("/send2") 45 | public Result send2(@RequestParam String mailVo, MultipartFile[] files) { 46 | MailVo mailVoReal = GsonUtil.fromJson(mailVo, MailVo.class); 47 | mailVoReal.setFiles(files); 48 | mailUtil.sendMail(mailVoReal); 49 | return Result.ok(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/sys/sys_user.sql: -------------------------------------------------------------------------------- 1 | -- DROP DATABASE IF EXISTS admin_template; 2 | -- 3 | -- CREATE DATABASE admin_template; 4 | 5 | -- --------------------------sys_user 用户表--------------------------------------- 6 | USE admin_template; 7 | DROP TABLE IF EXISTS sys_user; 8 | -- 用户表 9 | CREATE TABLE sys_user ( 10 | id bigint NOT NULL COMMENT '用户 ID', 11 | name varchar(20) NOT NULL COMMENT '用户名', 12 | mobile varchar(20) NOT NULL COMMENT '用户手机号', 13 | password varchar(64) NOT NULL COMMENT '用户密码', 14 | sex tinyint DEFAULT NULL COMMENT '性别, 0 表示女, 1 表示男', 15 | age tinyint DEFAULT NULL COMMENT '年龄', 16 | avatar varchar(255) DEFAULT NULL COMMENT '头像', 17 | email varchar(100) DEFAULT NULL COMMENT '邮箱', 18 | create_time datetime DEFAULT NULL COMMENT '创建时间', 19 | update_time datetime DEFAULT NULL COMMENT '修改时间', 20 | delete_flag tinyint DEFAULT NULL COMMENT '逻辑删除标志,0 表示未删除, 1 表示删除', 21 | disabled_flag tinyint DEFAULT NULL COMMENT '禁用标志, 0 表示未禁用, 1 表示禁用', 22 | wx_id varchar(128) DEFAULT NULL COMMENT '微信 openid(拓展字段、用于第三方微信登录)', 23 | qq_id varchar(128) DEFAULT NULL COMMENT 'QQ openid(拓展字段、用于第三方 QQ 登录)', 24 | PRIMARY KEY(id), 25 | UNIQUE INDEX(name, mobile) 26 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系统用户表'; 27 | 28 | 29 | -- 插入数据 30 | INSERT INTO `sys_user`(`id`, `name`, `mobile`, `password`, `sex`, `age`, `avatar`, `email`, `create_time`, `update_time`, `delete_flag`, `disabled_flag`, `wx_id`, `qq_id`) 31 | VALUES (1278601251755454466, 'superAdmin', '17730125031', 'e10adc3949ba59abbe56e057f20f883e', 1, 23, NULL, "m_17730125031@163.com", '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0, 0, NULL, NULL), 32 | (1278601251755451232, 'admin', '17730125032', 'e10adc3949ba59abbe56e057f20f883e', 1, 23, NULL, "m_17730125031@163.com", '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0, 0, NULL, NULL), 33 | (1278601251755456778, 'jack', '17730125033', 'e10adc3949ba59abbe56e057f20f883e', 1, 23, NULL, "m_17730125031@163.com", '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0, 0, NULL, NULL); 34 | 35 | -- --------------------------sys_user 用户表--------------------------------------- 36 | -------------------------------------------------------------------------------- /front/src/store/module/common.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // 开启命名空间(防止各模块间命名冲突),访问时需要使用 模块名 + 方法名 3 | namespaced: true, 4 | // 管理数据(状态) 5 | state: { 6 | // 用于保存语言设置(国际化),默认为中文 7 | language: 'zh', 8 | // 表示侧边栏选中的菜单项的名 9 | menuActiveName: '', 10 | // 表示标签页数据,数组 11 | mainTabs: [], 12 | // 表示标签页中选中的标签名 13 | mainTabsActiveName: '', 14 | // 用于保存动态路由的数据 15 | dynamicRoutes: [] 16 | }, 17 | // 更改 state(同步) 18 | mutations: { 19 | updateLanguage(state, data) { 20 | state.language = data 21 | }, 22 | updateMenuActiveName(state, name) { 23 | state.menuActiveName = name 24 | }, 25 | updateMainTabs(state, tabs) { 26 | state.mainTabs = tabs 27 | }, 28 | updateMainTabsActiveName(state, name) { 29 | state.mainTabsActiveName = name 30 | }, 31 | updateDynamicRoutes(state, routes) { 32 | state.dynamicRoutes = routes 33 | }, 34 | resetState(state) { 35 | let stateTemp = { 36 | language: 'zh', 37 | menuActiveName: '', 38 | mainTabs: [], 39 | mainTabsActiveName: '', 40 | dynamicRoutes: [] 41 | } 42 | Object.assign(state, stateTemp) 43 | } 44 | }, 45 | // 异步触发 mutations 46 | actions: { 47 | updateLanguage({commit, state}, data) { 48 | commit("updateLanguage", data) 49 | }, 50 | updateMenuActiveName({commit, state}, name) { 51 | commit("updateMenuActiveName", name) 52 | }, 53 | updateMainTabs({commit, state}, tabs) { 54 | commit("updateMainTabs", tabs) 55 | }, 56 | updateMainTabsActiveName({commit, state}, name) { 57 | commit("updateMainTabsActiveName", name) 58 | }, 59 | updateDynamicRoutes({commit, state}, routes) { 60 | commit("updateDynamicRoutes", routes) 61 | }, 62 | resetState({commit, state}) { 63 | commit("resetState") 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/entity/SysUser.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | /** 14 | *

15 | * 系统用户表 16 | *

17 | * 18 | * @author lyh 19 | * @since 2020-07-02 20 | */ 21 | @Data 22 | @EqualsAndHashCode(callSuper = false) 23 | @Accessors(chain = true) 24 | @ApiModel(value="SysUser对象", description="系统用户表") 25 | public class SysUser implements Serializable { 26 | 27 | private static final long serialVersionUID=1L; 28 | 29 | @ApiModelProperty(value = "用户 ID") 30 | @TableId(value = "id", type = IdType.ASSIGN_ID) 31 | private Long id; 32 | 33 | @ApiModelProperty(value = "用户名") 34 | private String name; 35 | 36 | @ApiModelProperty(value = "用户手机号") 37 | private String mobile; 38 | 39 | @ApiModelProperty(value = "用户密码") 40 | private String password; 41 | 42 | @ApiModelProperty(value = "性别, 0 表示女, 1 表示男") 43 | private Integer sex; 44 | 45 | @ApiModelProperty(value = "年龄") 46 | private Integer age; 47 | 48 | @ApiModelProperty(value = "头像") 49 | private String avatar; 50 | 51 | @ApiModelProperty(value = "邮箱") 52 | private String email; 53 | 54 | @TableField(fill = FieldFill.INSERT) 55 | @ApiModelProperty(value = "创建时间") 56 | private Date createTime; 57 | 58 | @TableField(fill = FieldFill.INSERT_UPDATE) 59 | @ApiModelProperty(value = "修改时间") 60 | private Date updateTime; 61 | 62 | @TableField(fill = FieldFill.INSERT) 63 | @TableLogic(value = "0", delval = "1") 64 | @ApiModelProperty(value = "逻辑删除标志,0 表示未删除, 1 表示删除") 65 | private Integer deleteFlag; 66 | 67 | @TableField(fill = FieldFill.INSERT) 68 | @ApiModelProperty(value = "禁用标志, 0 表示未禁用, 1 表示禁用") 69 | private Integer disabledFlag; 70 | 71 | @ApiModelProperty(value = "微信 openid(拓展字段、用于第三方微信登录)") 72 | private String wxId; 73 | 74 | @ApiModelProperty(value = "QQ openid(拓展字段、用于第三方 QQ 登录)") 75 | private String qqId; 76 | } 77 | -------------------------------------------------------------------------------- /back/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # 配置端口号 2 | server: 3 | port: 8000 4 | spring: 5 | # 配置数据源 6 | datasource: 7 | driver-class-name: com.mysql.cj.jdbc.Driver 8 | username: root 9 | password: 123456 10 | url: jdbc:mysql://localhost:3306/admin_template?useUnicode=true&characterEncoding=utf8 11 | # 配置环境(dev 开发环境、prod 生产环境) 12 | profiles: 13 | active: dev 14 | # 配置国际化,basename 指定国际化文件前缀 15 | messages: 16 | basename: static/i18n/messages 17 | encoding: UTF-8 18 | # Redis 配置 19 | redis: 20 | # Redis 服务器地址 21 | host: 120.26.184.41 22 | # 连接端口号 23 | port: 6379 24 | # 数据库索引(0 - 15) 25 | database: 0 26 | # 超时时间(毫秒) 27 | timeout: 10000 28 | # lettuce 参数 29 | lettuce: 30 | pool: 31 | # 最大连接数(使用负值表示没有限制) 默认为 8 32 | max-active: 10 33 | # 最大阻塞等待时间(使用负值表示没有限制) 默认为 -1 ms 34 | max-wait: -1 35 | # 最大空闲连接 默认为 8 36 | max-idle: 5 37 | # 最小空闲连接 默认为 0 38 | min-idle: 0 39 | # mail 配置 40 | mail: 41 | # SMTP 服务器地址 42 | host: smtp.163.com 43 | # 邮件服务器账号 44 | username: m_17730125031@163.com 45 | # 授权码 46 | password: KFYRVDQAUXLNBSUO 47 | # 配置端口号(默认使用 25,若项目发布到云服务器,需要开放相应端口 465,需要配置相关 ssl 协议) 48 | port: 465 49 | # 编码字符集采用 UTF-8 50 | default-encoding: UTF-8 51 | # 配置 ssl 协议(端口为 25 时,可以不用配置) 52 | properties: 53 | mail: 54 | smtp: 55 | ssl: 56 | enable: true 57 | socketFactory: 58 | port: 465 59 | class: javax.net.ssl.SSLSocketFactory 60 | # 文件上传大小配置 61 | servlet: 62 | multipart: 63 | # 限制单个文件大小 64 | max-file-size: 10MB 65 | # 限制单次请求总文件大小 66 | max-request-size: 50MB 67 | # 设置 json 中日期显示格式 68 | jackson: 69 | # 设置显示格式 70 | date-format: yyyy-MM-dd HH:mm:ss 71 | # 设置时区 72 | time-zone: GMT+8 73 | 74 | # 阿里云配置信息 75 | aliyun: 76 | # common 配置信息 77 | accessKeyId: LTAI4GEWZbLZocBzXKYEfmmq 78 | accessKeySecret: rZLsruKxWex2qGYVA3UsuBgW5B3uJQ 79 | # OSS 相关配置信息 80 | endPoint: http://oss-cn-beijing.aliyuncs.com 81 | bucketName: admin-vue-template 82 | domain: http://admin-vue-template.oss-cn-beijing.aliyuncs.com 83 | # SMS 短信服务 84 | regionId: cn-hangzhou 85 | signName: 后台管理系统 86 | templateCode: SMS_194050461 87 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import com.lyh.admin_template.back.common.validator.group.AddGroup; 5 | import com.lyh.admin_template.back.common.validator.group.UpdateGroup; 6 | import io.swagger.annotations.ApiModel; 7 | import io.swagger.annotations.ApiModelProperty; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.experimental.Accessors; 11 | 12 | import javax.validation.constraints.NotEmpty; 13 | import javax.validation.constraints.Pattern; 14 | import java.io.Serializable; 15 | import java.util.Date; 16 | 17 | /** 18 | *

19 | * 用户表 20 | *

21 | * 22 | * @author lyh 23 | * @since 2020-06-08 24 | */ 25 | @Data 26 | @EqualsAndHashCode(callSuper = false) 27 | @Accessors(chain = true) 28 | @TableName("back_user") 29 | @ApiModel(value="User对象", description="用户表") 30 | public class User implements Serializable { 31 | 32 | private static final long serialVersionUID=1L; 33 | 34 | @ApiModelProperty(value = "用户 ID") 35 | @TableId(value = "id", type = IdType.ASSIGN_ID) 36 | private Long id; 37 | 38 | @NotEmpty(message = "{user.name.empty}", groups = {AddGroup.class, UpdateGroup.class}) 39 | @ApiModelProperty(value = "用户名") 40 | private String name; 41 | 42 | @NotEmpty(message = "{user.mobile.empty}", groups = {AddGroup.class, UpdateGroup.class}) 43 | @Pattern(message = "手机号格式不合法", regexp = "^[1-9]{1}\\d{10}$", groups = {AddGroup.class, UpdateGroup.class}) 44 | @ApiModelProperty(value = "用户手机号") 45 | private String mobile; 46 | 47 | @NotEmpty(message = "{user.password.empty}", groups = {AddGroup.class, UpdateGroup.class}) 48 | @ApiModelProperty(value = "用户密码") 49 | private String password; 50 | 51 | @TableField(fill = FieldFill.INSERT) 52 | @ApiModelProperty(value = "创建时间") 53 | private Date createTime; 54 | 55 | @TableField(fill = FieldFill.INSERT_UPDATE) 56 | @ApiModelProperty(value = "修改时间") 57 | private Date updateTime; 58 | 59 | @TableField(fill = FieldFill.INSERT) 60 | @TableLogic(value = "0", delval = "1") 61 | @ApiModelProperty(value = "逻辑删除标志,0 表示未删除, 1 表示删除") 62 | private Integer deleteFlag; 63 | 64 | @Version 65 | @TableField(fill = FieldFill.INSERT) 66 | @ApiModelProperty(value = "版本号") 67 | private Integer version; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.exception; 2 | 3 | import com.lyh.admin_template.back.common.utils.Result; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.validation.BindingResult; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * 全局异常处理类。 16 | * 使用 slf4j 保存日志信息。 17 | * 此处使用了 统一结果处理 类 Result 用于包装异常信息。 18 | */ 19 | @RestControllerAdvice 20 | public class GlobalExceptionHandler { 21 | private Logger logger = LoggerFactory.getLogger(getClass()); 22 | 23 | /** 24 | * 处理 Exception 异常 25 | * @param e 异常 26 | * @return 处理结果 27 | */ 28 | @ExceptionHandler(Exception.class) 29 | public Result handlerException(Exception e) { 30 | logger.error(e.getMessage(), e); 31 | return Result.error().message(e.getMessage()); 32 | } 33 | 34 | /** 35 | * 处理空指针异常 36 | * @param e 异常 37 | * @return 处理结果 38 | */ 39 | @ExceptionHandler(NullPointerException.class) 40 | public Result handlerNullPointerException(NullPointerException e) { 41 | logger.error(e.getMessage(), e); 42 | return Result.error().message("空指针异常"); 43 | } 44 | 45 | /** 46 | * 处理自定义异常 47 | * @param e 异常 48 | * @return 处理结果 49 | */ 50 | @ExceptionHandler(GlobalException.class) 51 | public Result handlerGlobalException(GlobalException e) { 52 | logger.error(e.getMessage(), e); 53 | return Result.error().message(e.getMessage()).code(e.getCode()); 54 | } 55 | 56 | /** 57 | * 处理 JSR303 校验的异常 58 | * @param e 异常 59 | * @return 处理结果 60 | */ 61 | @ExceptionHandler(MethodArgumentNotValidException.class) 62 | public Result handlerValidException(MethodArgumentNotValidException e) { 63 | logger.error(e.getMessage(), e); 64 | BindingResult result = e.getBindingResult(); 65 | Map map = new HashMap<>(); 66 | // 获取校验结果,遍历获取捕获到的每个校验结果 67 | result.getFieldErrors().forEach(item ->{ 68 | // 存储得到的校验结果 69 | map.put(item.getField(), item.getDefaultMessage()); 70 | }); 71 | return Result.error().message("数据校验不合法").data("items", map); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/service/impl/SysUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.lyh.admin_template.back.modules.sys.entity.SysRole; 6 | import com.lyh.admin_template.back.modules.sys.entity.SysUser; 7 | import com.lyh.admin_template.back.modules.sys.entity.SysUserRole; 8 | import com.lyh.admin_template.back.modules.sys.mapper.SysUserMapper; 9 | import com.lyh.admin_template.back.modules.sys.service.SysRoleService; 10 | import com.lyh.admin_template.back.modules.sys.service.SysUserRoleService; 11 | import com.lyh.admin_template.back.modules.sys.service.SysUserService; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Isolation; 15 | import org.springframework.transaction.annotation.Propagation; 16 | import org.springframework.transaction.annotation.Transactional; 17 | 18 | /** 19 | *

20 | * 系统用户表 服务实现类 21 | *

22 | * 23 | * @author lyh 24 | * @since 2020-07-02 25 | */ 26 | @Service 27 | public class SysUserServiceImpl extends ServiceImpl implements SysUserService { 28 | 29 | @Autowired 30 | private SysRoleService sysRoleService; 31 | @Autowired 32 | private SysUserRoleService sysUserRoleService; 33 | 34 | /** 35 | * 先插入数据到 用户表 sys_user 中。 36 | * 再获取数据 ID 与 角色 ID 并插入到 用户角色表 sys_user_role 中。 37 | * @param sysUser 用户数据 38 | * @return true 表示插入成功, false 表示失败 39 | */ 40 | @Override 41 | @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout=36000,rollbackFor=Exception.class) 42 | public boolean saveUser(SysUser sysUser) { 43 | // 向 sys_user 表中插入数据 44 | if (this.save(sysUser)) { 45 | // 获取当前用户的 ID 46 | QueryWrapper queryWrapper = new QueryWrapper(); 47 | queryWrapper.eq("name", sysUser.getName()); 48 | SysUser sysUser2 = this.getOne(queryWrapper); 49 | 50 | // 获取普通用户角色 ID 51 | QueryWrapper queryWrapper2 = new QueryWrapper(); 52 | queryWrapper2.eq("role_name", "user"); 53 | SysRole sysRole = sysRoleService.getOne(queryWrapper2); 54 | 55 | // 插入到 用户-角色 表中(sys_user_role) 56 | SysUserRole sysUserRole = new SysUserRole(); 57 | sysUserRole.setUserId(sysUser2.getId()).setRoleId(sysRole.getId()); 58 | return sysUserRoleService.save(sysUserRole); 59 | } 60 | return false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /front/src/views/home/UpdatePassword.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/config/LocaleConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.support.ResourceBundleMessageSource; 6 | import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; 7 | import org.springframework.web.servlet.LocaleResolver; 8 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 10 | import org.springframework.web.servlet.i18n.CookieLocaleResolver; 11 | import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 12 | 13 | import javax.validation.Validator; 14 | import java.util.Locale; 15 | 16 | /** 17 | * 配置国际化语言 18 | */ 19 | @Configuration 20 | public class LocaleConfig { 21 | 22 | /** 23 | * 默认解析器 24 | */ 25 | @Bean 26 | public LocaleResolver localeResolver() { 27 | CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); 28 | cookieLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); 29 | cookieLocaleResolver.setCookieName("language"); 30 | cookieLocaleResolver.setCookieMaxAge(-1); 31 | return cookieLocaleResolver; 32 | } 33 | 34 | /** 35 | * 定义拦截器,其中 language 表示切换语言的参数名。 36 | * 拦截请求后,根据请求中的 language 参数去设置语言。 37 | */ 38 | @Bean 39 | public WebMvcConfigurer localeInterceptor() { 40 | return new WebMvcConfigurer() { 41 | @Override 42 | public void addInterceptors(InterceptorRegistry registry) { 43 | LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor(); 44 | localeInterceptor.setParamName("language"); 45 | registry.addInterceptor(localeInterceptor); 46 | } 47 | }; 48 | } 49 | 50 | /** 51 | * 由于返回类型不是 MessageSource 类型,所以需要在 @Bean 注解中指明 name 参数,用于自动装配到 MessageSource 中。 52 | * 使用时 通过 @Autowired 注入 MessageSource 即可。 53 | * setBasenames 用于指定国际化资源文件前缀 54 | * setDefaultEncoding 用于指定编码字符集 55 | */ 56 | @Bean(name = "messageSource") 57 | public ResourceBundleMessageSource getMessageSource() throws Exception { 58 | ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); 59 | resourceBundleMessageSource.setDefaultEncoding("UTF-8"); 60 | resourceBundleMessageSource.setBasenames("static/i18n/messages"); 61 | return resourceBundleMessageSource; 62 | } 63 | 64 | /** 65 | * 配置 JSR303 国际化问题,让其加载国际化资源文件 66 | * @return 67 | * @throws Exception 68 | */ 69 | @Bean 70 | public Validator getValidator() throws Exception { 71 | LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); 72 | validator.setValidationMessageSource(getMessageSource()); 73 | return validator; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/SmsUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import com.aliyuncs.CommonRequest; 4 | import com.aliyuncs.CommonResponse; 5 | import com.aliyuncs.DefaultAcsClient; 6 | import com.aliyuncs.IAcsClient; 7 | import com.aliyuncs.http.MethodType; 8 | import com.aliyuncs.profile.DefaultProfile; 9 | import com.lyh.admin_template.back.modules.sms.entity.SmsResponse; 10 | import lombok.Data; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.stereotype.Component; 14 | 15 | /** 16 | * sms 短信发送工具类 17 | */ 18 | @Data 19 | @Component 20 | public class SmsUtil { 21 | @Value("${aliyun.accessKeyId}") 22 | private String accessKeyId; 23 | @Value("${aliyun.accessKeySecret}") 24 | private String accessKeySecret; 25 | @Value("${aliyun.signName}") 26 | private String signName; 27 | @Value("${aliyun.templateCode}") 28 | private String templateCode; 29 | @Value("${aliyun.regionId}") 30 | private String regionId; 31 | private final static String OK = "OK"; 32 | 33 | /** 34 | * 发送短信 35 | */ 36 | public String sendSms(String phoneNumbers) { 37 | if (StringUtils.isEmpty(phoneNumbers)) { 38 | return null; 39 | } 40 | DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); 41 | IAcsClient client = new DefaultAcsClient(profile); 42 | 43 | CommonRequest request = new CommonRequest(); 44 | // 固定参数,无需修改 45 | request.setSysMethod(MethodType.POST); 46 | request.setSysDomain("dysmsapi.aliyuncs.com"); 47 | request.setSysVersion("2017-05-25"); 48 | request.setSysAction("SendSms"); 49 | request.putQueryParameter("RegionId", regionId); 50 | 51 | // 设置手机号 52 | request.putQueryParameter("PhoneNumbers", phoneNumbers); 53 | // 设置签名模板 54 | request.putQueryParameter("SignName", signName); 55 | // 设置短信模板 56 | request.putQueryParameter("TemplateCode", templateCode); 57 | // 设置短信验证码 58 | String code = getCode(); 59 | request.putQueryParameter("TemplateParam", "{\"code\":" + code +"}"); 60 | try { 61 | CommonResponse response = client.getCommonResponse(request); 62 | System.out.println(response.getData()); 63 | // 转换返回的数据(需引入 Gson 依赖) 64 | SmsResponse smsResponse = GsonUtil.fromJson(response.getData(), SmsResponse.class); 65 | // 当 message 与 code 均为 ok 时,短信发送成功、否则失败 66 | if (SmsUtil.OK.equals(smsResponse.getMessage()) && SmsUtil.OK.equals(smsResponse.getCode())) { 67 | return code; 68 | } 69 | return null; 70 | } catch (Exception e) { 71 | throw new RuntimeException(e); 72 | } 73 | } 74 | 75 | /** 76 | * 获取 6 位验证码 77 | */ 78 | public String getCode() { 79 | return String.valueOf((int)((Math.random()*9+1)*100000)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jws; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.SignatureAlgorithm; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import java.util.Date; 11 | 12 | /** 13 | * JWT 操作工具类 14 | */ 15 | public class JwtUtil { 16 | 17 | // 设置默认过期时间(15 分钟) 18 | private static final long DEFAULT_EXPIRE = 1000L * 60 * 15; 19 | // 设置 jwt 生成 secret(随意指定) 20 | private static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; 21 | 22 | /** 23 | * 生成 jwt token,并指定默认过期时间 15 分钟 24 | */ 25 | public static String getJwtToken(Object data) { 26 | return getJwtToken(data, DEFAULT_EXPIRE); 27 | } 28 | 29 | /** 30 | * 生成 jwt token,根据指定的 过期时间 31 | */ 32 | public static String getJwtToken(Object data, Long expire) { 33 | String JwtToken = Jwts.builder() 34 | // 设置 jwt 类型 35 | .setHeaderParam("typ", "JWT") 36 | // 设置 jwt 加密方法 37 | .setHeaderParam("alg", "HS256") 38 | // 设置 jwt 主题 39 | .setSubject("admin-user") 40 | // 设置 jwt 发布时间 41 | .setIssuedAt(new Date()) 42 | // 设置 jwt 过期时间 43 | .setExpiration(new Date(System.currentTimeMillis() + expire)) 44 | // 设置自定义数据 45 | .claim("data", data) 46 | // 设置密钥与算法 47 | .signWith(SignatureAlgorithm.HS256, APP_SECRET) 48 | // 生成 token 49 | .compact(); 50 | return JwtToken; 51 | } 52 | 53 | /** 54 | * 判断token是否存在与有效,true 表示未过期,false 表示过期或不存在 55 | */ 56 | public static boolean checkToken(String jwtToken) { 57 | if (StringUtils.isEmpty(jwtToken)) { 58 | return false; 59 | } 60 | try { 61 | // 获取 token 数据 62 | Jws claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); 63 | // 判断是否过期 64 | return claimsJws.getBody().getExpiration().after(new Date()); 65 | } catch (Exception e) { 66 | throw new RuntimeException(e); 67 | } 68 | } 69 | 70 | /** 71 | * 判断token是否存在与有效 72 | */ 73 | public static boolean checkToken(HttpServletRequest request) { 74 | return checkToken(request.getHeader("token")); 75 | } 76 | 77 | /** 78 | * 根据 token 获取数据 79 | */ 80 | public static Claims getTokenBody(HttpServletRequest request) { 81 | return getTokenBody(request.getHeader("token")); 82 | } 83 | 84 | /** 85 | * 根据 token 获取数据 86 | */ 87 | public static Claims getTokenBody(String jwtToken) { 88 | if (StringUtils.isEmpty(jwtToken)) { 89 | return null; 90 | } 91 | Jws claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); 92 | return claimsJws.getBody(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /front/src/mock/modules/menu.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | // 登录 4 | export function getMenus() { 5 | return { 6 | // isOpen: false, 7 | url: 'api/menu/getMenus', 8 | type: 'get', 9 | data: { 10 | 'code': 200, 11 | 'msg': 'success', 12 | 'data': menuList 13 | } 14 | } 15 | } 16 | 17 | /* 18 | menuId 表示当前菜单项 ID 19 | parentId 表示父菜单项 ID 20 | name_zh 表示菜单名(中文) 21 | name_en 表示菜单名(英语) 22 | url 表示路由跳转路径(自身模块 或者 外部 url) 23 | type 表示当前菜单项的级别(0: 目录,1: 菜单项,2: 按钮) 24 | icon 表示当前菜单项的图标 25 | orderNum 表示当前菜单项的顺序 26 | subMenuList 表示当前菜单项的子菜单 27 | */ 28 | var menuList = [{ 29 | 'menuId': 1, 30 | 'parentId': 0, 31 | 'name_zh': '系统管理', 32 | 'name_en': 'System Control', 33 | 'url': '', 34 | 'type': 0, 35 | 'icon': 'el-icon-setting', 36 | 'orderNum': 0, 37 | 'subMenuList': [{ 38 | 'menuId': 2, 39 | 'parentId': 1, 40 | 'name_zh': '管理员列表', 41 | 'name_en': 'Administrator List', 42 | 'url': 'sys/UserList', 43 | 'type': 1, 44 | 'icon': 'el-icon-user', 45 | 'orderNum': 1, 46 | 'subMenuList': [] 47 | }, 48 | { 49 | 'menuId': 3, 50 | 'parentId': 1, 51 | 'name_zh': '角色管理', 52 | 'name_en': 'Role Control', 53 | 'url': 'sys/RoleControl', 54 | 'type': 1, 55 | 'icon': 'el-icon-price-tag', 56 | 'orderNum': 2, 57 | 'subMenuList': [] 58 | }, 59 | { 60 | 'menuId': 4, 61 | 'parentId': 1, 62 | 'name_zh': '菜单管理', 63 | 'name_en': 'Menu Control', 64 | 'url': 'sys/MenuControl', 65 | 'type': 1, 66 | 'icon': 'el-icon-menu', 67 | 'orderNum': 3, 68 | 'subMenuList': [] 69 | } 70 | ] 71 | }, 72 | { 73 | 'menuId': 5, 74 | 'parentId': 0, 75 | 'name_zh': '帮助', 76 | 'name_en': 'Help', 77 | 'url': '', 78 | 'type': 0, 79 | 'icon': 'el-icon-info', 80 | 'orderNum': 1, 81 | 'subMenuList': [{ 82 | 'menuId': 6, 83 | 'parentId': 5, 84 | 'name_zh': '百度', 85 | 'name_en': 'Baidu', 86 | 'url': 'https://www.baidu.com/', 87 | 'type': 1, 88 | 'icon': 'el-icon-menu', 89 | 'orderNum': 1, 90 | 'subMenuList': [] 91 | }, { 92 | 'menuId': 7, 93 | 'parentId': 5, 94 | 'name_zh': '博客', 95 | 'name_en': 'Blog', 96 | 'url': 'https://www.cnblogs.com/l-y-h/', 97 | 'type': 1, 98 | 'icon': 'el-icon-menu', 99 | 'orderNum': 1, 100 | 'subMenuList': [] 101 | }] 102 | } 103 | ] -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/MailUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import com.lyh.admin_template.back.vo.MailVo; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.mail.javamail.JavaMailSender; 6 | import org.springframework.mail.javamail.MimeMessageHelper; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.multipart.MultipartFile; 9 | 10 | import javax.mail.MessagingException; 11 | import java.util.Date; 12 | 13 | /** 14 | * 邮件发送 工具类 15 | */ 16 | @Component 17 | public class MailUtil { 18 | /** 19 | * 用于操作邮件 20 | */ 21 | @Autowired 22 | private JavaMailSender javaMailSender; 23 | 24 | /** 25 | * 发送邮件 26 | */ 27 | public void sendMail(MailVo mailVo) { 28 | // 检查邮件地址是否正确 29 | if (checkMail(mailVo)) { 30 | // 发送邮件 31 | sendMailReal(mailVo); 32 | } else { 33 | throw new RuntimeException("邮件地址异常"); 34 | } 35 | } 36 | 37 | /** 38 | * 邮件必须项检查 39 | */ 40 | private boolean checkMail(MailVo mailVo) { 41 | return checkAddress(mailVo.getFrom()) && checkAddress(mailVo.getTo()) 42 | && checkAddress(mailVo.getCc()) && checkAddress(mailVo.getBcc()); 43 | } 44 | 45 | /** 46 | * 检查邮箱地址是否正确 47 | */ 48 | private boolean checkAddress(String... address) { 49 | if (address.length == 0 || address == null) { 50 | return true; 51 | } 52 | String regex = "\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}"; 53 | for (String item : address) { 54 | if (!item.matches(regex)) { 55 | return false; 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | /** 62 | * 发送邮件真实操作 63 | */ 64 | private void sendMailReal(MailVo mailVo) { 65 | // 构造一个邮件助手 66 | MimeMessageHelper mimeMessageHelper = null; 67 | try { 68 | // 传入 MimeMessage,并对其进行一系列操作,true 表示支持复杂邮件(支持附件等) 69 | mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(), true); 70 | // 设置邮件发送人 71 | mimeMessageHelper.setFrom(mailVo.getFrom()); 72 | // 设置邮件接收人 73 | mimeMessageHelper.setTo(mailVo.getTo()); 74 | // 设置邮件抄送人 75 | mimeMessageHelper.setCc(mailVo.getCc()); 76 | // 设置邮件密送人 77 | mimeMessageHelper.setBcc(mailVo.getBcc()); 78 | // 设置邮件主题 79 | mimeMessageHelper.setSubject(mailVo.getSubject()); 80 | // 设置邮件内容 81 | mimeMessageHelper.setText(mailVo.getText()); 82 | // 设置邮件日期 83 | mimeMessageHelper.setSentDate(new Date()); 84 | // 设置附件 85 | for (MultipartFile file : mailVo.getFiles()) { 86 | mimeMessageHelper.addAttachment(file.getOriginalFilename(), file); 87 | } 88 | 89 | // 发送邮件 90 | javaMailSender.send(mimeMessageHelper.getMimeMessage()); 91 | } catch (MessagingException e) { 92 | throw new RuntimeException("邮件发送失败"); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.config; 2 | 3 | import com.google.common.collect.Lists; 4 | import io.swagger.annotations.ApiOperation; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Profile; 8 | import springfox.documentation.builders.ApiInfoBuilder; 9 | import springfox.documentation.builders.PathSelectors; 10 | import springfox.documentation.builders.RequestHandlerSelectors; 11 | import springfox.documentation.service.ApiInfo; 12 | import springfox.documentation.service.ApiKey; 13 | import springfox.documentation.service.AuthorizationScope; 14 | import springfox.documentation.service.SecurityReference; 15 | import springfox.documentation.spi.DocumentationType; 16 | import springfox.documentation.spi.service.contexts.SecurityContext; 17 | import springfox.documentation.spring.web.plugins.Docket; 18 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | @Configuration 24 | @EnableSwagger2 25 | @Profile({"dev","test"}) 26 | public class SwaggerConfig { 27 | 28 | @Bean 29 | public Docket createRestApi() { 30 | return new Docket(DocumentationType.SWAGGER_2) 31 | .apiInfo(apiInfo()) 32 | .select() 33 | // 加了ApiOperation注解的类,才会生成接口文档 34 | .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) 35 | // 指定包下的类,才生成接口文档 36 | .apis(RequestHandlerSelectors.basePackage("com.lyh.admin_template.back")) 37 | .paths(PathSelectors.any()) 38 | .build() 39 | .securitySchemes(security()) 40 | .securityContexts(securityContexts()); 41 | } 42 | 43 | private ApiInfo apiInfo() { 44 | return new ApiInfoBuilder() 45 | .title("Swagger 测试") 46 | .description("Swagger 测试文档") 47 | .termsOfServiceUrl("https://www.cnblogs.com/l-y-h/") 48 | .version("1.0.0") 49 | .build(); 50 | } 51 | 52 | private List security() { 53 | return Lists.newArrayList( 54 | new ApiKey("token", "token", "header") 55 | ); 56 | } 57 | 58 | private List securityContexts() { 59 | return Lists.newArrayList( 60 | SecurityContext.builder().securityReferences(defaultAuth()) 61 | //过滤要验证的路径 62 | .forPaths(PathSelectors.regex("^(?!auth).*$")) 63 | .build() 64 | ); 65 | } 66 | 67 | //增加全局认证 68 | List defaultAuth() { 69 | AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); 70 | AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; 71 | authorizationScopes[0] = authorizationScope; 72 | List securityReferences = new ArrayList<>(); 73 | // 由于 securitySchemes() 方法中 header 写入值为 token,所以此处为 token 74 | securityReferences.add(new SecurityReference("token", authorizationScopes)); 75 | return securityReferences; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/oss/controller/BackOssController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.oss.controller; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.lyh.admin_template.back.common.exception.GlobalException; 6 | import com.lyh.admin_template.back.common.utils.OssUtil; 7 | import com.lyh.admin_template.back.common.utils.Result; 8 | import com.lyh.admin_template.back.modules.oss.entity.BackOss; 9 | import com.lyh.admin_template.back.modules.oss.service.BackOssService; 10 | import io.swagger.annotations.Api; 11 | import io.swagger.annotations.ApiOperation; 12 | import io.swagger.annotations.ApiParam; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.web.bind.annotation.*; 15 | import org.springframework.web.multipart.MultipartFile; 16 | 17 | import java.io.IOException; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | *

23 | * 文件上传 前端控制器 24 | *

25 | * 26 | * @author lyh 27 | * @since 2020-06-19 28 | */ 29 | @RestController 30 | @RequestMapping("/oss/back-oss") 31 | @Api(tags = "文件上传") 32 | public class BackOssController { 33 | 34 | @Autowired 35 | private OssUtil ossUtil; 36 | @Autowired 37 | private BackOssService backOssService; 38 | 39 | @ApiOperation(value = "删除文件") 40 | @DeleteMapping("/delete/object") 41 | public Result deleteObject(@RequestParam String key) { 42 | ossUtil.deleteObject(key); 43 | QueryWrapper queryWrapper = new QueryWrapper(); 44 | queryWrapper.eq("oss_name", key); 45 | backOssService.remove(queryWrapper); 46 | return Result.ok(); 47 | } 48 | 49 | @ApiOperation(value = "获取签名数据") 50 | @GetMapping("/policy") 51 | public Result policy() { 52 | return Result.ok().data("policyData", ossUtil.getPolicy()); 53 | } 54 | 55 | @ApiOperation(value = "保存并获取文件 url") 56 | @PostMapping("/saveUrl") 57 | public Result saveUrl(@RequestParam String key, @RequestParam String fileName) { 58 | BackOss backOss = new BackOss(); 59 | backOss.setOssName(key); 60 | backOss.setFileName(fileName); 61 | backOss.setFileUrl(ossUtil.getUrl(key)); 62 | QueryWrapper queryWrapper = new QueryWrapper(); 63 | queryWrapper.eq("oss_name", key); 64 | // 数据不存在则添加,否则根据 oss_name 更新数据 65 | backOssService.saveOrUpdate(backOss, queryWrapper); 66 | return Result.ok().data("file", backOssService.getOne(queryWrapper)); 67 | } 68 | 69 | @ApiOperation(value = "上传文件") 70 | @PostMapping("/upload") 71 | public Result upload(@ApiParam MultipartFile file) { 72 | // 用于保存文件 url 73 | String url = null; 74 | // 用于保存文件信息 75 | BackOss backOss = new BackOss(); 76 | try { 77 | // 获取文件上传路径 78 | url = ossUtil.uploadSuffix(file.getInputStream(), "aliyun", file.getOriginalFilename()); 79 | 80 | // 保存文件路径到数据库中 81 | backOss.setFileName(file.getOriginalFilename()); 82 | backOss.setOssName(url); 83 | backOss.setFileUrl(ossUtil.getUrl(url)); 84 | backOssService.save(backOss); 85 | } catch (IOException e) { 86 | throw new GlobalException("文件上传失败"); 87 | } 88 | return Result.ok().message("文件上传成功").data("file", backOss); 89 | } 90 | 91 | @ApiOperation(value = "获取所有文件信息") 92 | @GetMapping("/getAll") 93 | public Result getAll() { 94 | return Result.ok().data("file", backOssService.list()); 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /back/src/test/java/com/lyh/admin_template/back/TestAutoGenerator.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.generator.AutoGenerator; 5 | import com.baomidou.mybatisplus.generator.config.DataSourceConfig; 6 | import com.baomidou.mybatisplus.generator.config.GlobalConfig; 7 | import com.baomidou.mybatisplus.generator.config.PackageConfig; 8 | import com.baomidou.mybatisplus.generator.config.StrategyConfig; 9 | import com.baomidou.mybatisplus.generator.config.rules.DateType; 10 | import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | 14 | @SpringBootTest 15 | public class TestAutoGenerator { 16 | @Test 17 | public void autoGenerate() { 18 | // Step1:代码生成器 19 | AutoGenerator mpg = new AutoGenerator(); 20 | 21 | // Step2:全局配置 22 | GlobalConfig gc = new GlobalConfig(); 23 | // 填写代码生成的目录(需要修改) 24 | String projectPath = "E:\\myProject\\myGit\\admin-vue-template\\back"; 25 | // 拼接出代码最终输出的目录 26 | gc.setOutputDir(projectPath + "/src/main/java"); 27 | // 配置开发者信息(可选)(需要修改) 28 | gc.setAuthor("lyh"); 29 | // 配置是否打开目录,false 为不打开(可选) 30 | gc.setOpen(false); 31 | // 实体属性 Swagger2 注解,添加 Swagger 依赖,开启 Swagger2 模式(可选) 32 | gc.setSwagger2(true); 33 | // 重新生成文件时是否覆盖,false 表示不覆盖(可选) 34 | gc.setFileOverride(false); 35 | // 配置主键生成策略,此处为 ASSIGN_ID(可选) 36 | gc.setIdType(IdType.ASSIGN_ID); 37 | // 配置日期类型,此处为 ONLY_DATE(可选) 38 | gc.setDateType(DateType.ONLY_DATE); 39 | // 默认生成的 service 会有 I 前缀 40 | gc.setServiceName("%sService"); 41 | mpg.setGlobalConfig(gc); 42 | 43 | // Step3:数据源配置(需要修改) 44 | DataSourceConfig dsc = new DataSourceConfig(); 45 | // 配置数据库 url 地址 46 | dsc.setUrl("jdbc:mysql://localhost:3306/admin_template?useUnicode=true&characterEncoding=utf8"); 47 | // dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定数据库名 48 | // 配置数据库驱动 49 | dsc.setDriverName("com.mysql.cj.jdbc.Driver"); 50 | // 配置数据库连接用户名 51 | dsc.setUsername("root"); 52 | // 配置数据库连接密码 53 | dsc.setPassword("123456"); 54 | mpg.setDataSource(dsc); 55 | 56 | // Step:4:包配置 57 | PackageConfig pc = new PackageConfig(); 58 | // 配置父包名(需要修改) 59 | pc.setParent("com.lyh.admin_template"); 60 | // 配置模块名(需要修改) 61 | pc.setModuleName("back"); 62 | // 配置 entity 包名 63 | pc.setEntity("entity"); 64 | // 配置 mapper 包名 65 | pc.setMapper("mapper"); 66 | // 配置 service 包名 67 | pc.setService("service"); 68 | // 配置 controller 包名 69 | pc.setController("controller"); 70 | mpg.setPackageInfo(pc); 71 | 72 | // Step5:策略配置(数据库表配置) 73 | StrategyConfig strategy = new StrategyConfig(); 74 | // 指定表名(可以同时操作多个表,使用 , 隔开)(需要修改) 75 | strategy.setInclude("back_user"); 76 | // 配置数据表与实体类名之间映射的策略 77 | strategy.setNaming(NamingStrategy.underline_to_camel); 78 | // 配置数据表的字段与实体类的属性名之间映射的策略 79 | strategy.setColumnNaming(NamingStrategy.underline_to_camel); 80 | // 配置 lombok 模式 81 | strategy.setEntityLombokModel(true); 82 | // 配置 rest 风格的控制器(@RestController) 83 | strategy.setRestControllerStyle(true); 84 | // 配置驼峰转连字符 85 | strategy.setControllerMappingHyphenStyle(true); 86 | // 配置表前缀,生成实体时去除表前缀 87 | // 此处的表名为 test_mybatis_plus_user,模块名为 test_mybatis_plus,去除前缀后剩下为 user。 88 | strategy.setTablePrefix(pc.getModuleName() + "_"); 89 | mpg.setStrategy(strategy); 90 | 91 | // Step6:执行代码生成操作 92 | mpg.execute(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/sys/sys_menu.sql: -------------------------------------------------------------------------------- 1 | -- DROP DATABASE IF EXISTS admin_template; 2 | -- 3 | -- CREATE DATABASE admin_template; 4 | 5 | -- --------------------------sys_menu 菜单权限表--------------------------------------- 6 | USE admin_template; 7 | DROP TABLE IF EXISTS sys_menu; 8 | -- 系统菜单权限表 9 | CREATE TABLE sys_menu ( 10 | menu_id bigint NOT NULL COMMENT '当前菜单 ID', 11 | parent_id bigint NOT NULL COMMENT '当前菜单父菜单 ID', 12 | name_zh varchar(20) NOT NULL COMMENT '中文菜单名称', 13 | name_en varchar(40) NOT NULL COMMENT '英文菜单名称', 14 | type tinyint NOT NULL COMMENT '菜单类型,0 表示目录,1 表示菜单项,2 表示按钮', 15 | url varchar(100) NOT NULL COMMENT '访问路径', 16 | icon varchar(100) DEFAULT NULL COMMENT '菜单图标', 17 | order_num int DEFAULT NULL COMMENT '菜单项顺序', 18 | create_time datetime DEFAULT NULL COMMENT '创建时间', 19 | update_time datetime DEFAULT NULL COMMENT '修改时间', 20 | delete_flag tinyint DEFAULT NULL COMMENT '逻辑删除标志,0 表示未删除, 1 表示删除', 21 | PRIMARY KEY(menu_id) 22 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系统菜单权限表'; 23 | 24 | -- 插入数据 25 | INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name_zh`, `name_en`, `type`, `url`, `icon`, `order_num`, `create_time`, `update_time`, `delete_flag`) 26 | VALUES (1278601251755451111, 0, '系统管理', 'System Control', 0, '', 'el-icon-setting', 0,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 27 | (1278601251755452211, 1278601251755451111, '用户管理', 'User Control', 1, 'sys/UserList', 'el-icon-user', 1,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 28 | (1278601251755453311, 1278601251755451111, '角色管理', 'Role Control', 1, 'sys/RoleControl', 'el-icon-price-tag', 2,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 29 | (1278601251755454411, 1278601251755451111, '菜单管理', 'Menu Control', 1, 'sys/MenuControl', 'el-icon-menu', 3,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 30 | 31 | (1278601251755452221, 1278601251755452211, '添加', 'Add', 2, '', '', 1,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 32 | (1278601251755452231, 1278601251755452211, '删除', 'Delete', 2, '', '', 2,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 33 | (1278601251755452241, 1278601251755452211, '修改', 'Update', 2, '', '', 3,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 34 | (1278601251755452251, 1278601251755452211, '查看', 'List', 2, '', '', 4,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 35 | 36 | (1278601251755453321, 1278601251755453311, '添加', 'Add', 2, '', '', 1,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 37 | (1278601251755453331, 1278601251755453311, '删除', 'Delete', 2, '', '', 2,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 38 | (1278601251755453341, 1278601251755453311, '修改', 'Update', 2, '', '', 3,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 39 | (1278601251755453351, 1278601251755453311, '查看', 'List', 2, '', '', 4,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 40 | 41 | (1278601251755454421, 1278601251755454411, '添加', 'Add', 2, '', '', 1,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 42 | (1278601251755454431, 1278601251755454411, '删除', 'Delete', 2, '', '', 2,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 43 | (1278601251755454441, 1278601251755454411, '修改', 'Update', 2, '', '', 3,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 44 | (1278601251755454451, 1278601251755454411, '查看', 'List', 2, '', '', 4,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 45 | 46 | (1278601251755455511, 0, '帮助', 'help', 0, '', 'el-icon-info', 1,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 47 | (1278601251755455521, 1278601251755455511, '百度', 'Baidu', 1, 'https://www.baidu.com/', 'el-icon-menu', 1,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 48 | (1278601251755455531, 1278601251755455511, '博客', 'Blog', 1, 'https://www.cnblogs.com/l-y-h/', 'el-icon-menu', 2,'2020-07-02 16:07:48', '2020-07-02 16:07:48', 0); 49 | 50 | -- --------------------------sys_menu 菜单权限表--------------------------------------- 51 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/Result.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import lombok.Data; 4 | import org.apache.http.HttpStatus; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * 统一结果返回类。方法采用链式调用的写法(即返回类本身 return this)。 11 | * 构造器私有,不允许进行实例化,但提供静态方法 ok、error 返回一个实例。 12 | * 静态方法说明: 13 | * ok 返回一个 成功操作 的结果(实例对象)。 14 | * error 返回一个 失败操作 的结果(实例对象)。 15 | * 16 | * 普通方法说明: 17 | * success 用于自定义响应是否成功 18 | * code 用于自定义响应状态码 19 | * message 用于自定义响应消息 20 | * data 用于自定义响应数据 21 | * 22 | * 依赖信息说明: 23 | * 此处使用 @Data 注解,需导入 lombok 相关依赖文件。 24 | * 使用 HttpStatus 的常量表示 响应状态码,需导入 httpcore 相关依赖文件。 25 | */ 26 | @Data 27 | public class Result { 28 | /** 29 | * 响应是否成功,true 为成功,false 为失败 30 | */ 31 | private Boolean success; 32 | 33 | /** 34 | * 响应状态码, 200 成功,500 系统异常 35 | */ 36 | private Integer code; 37 | 38 | /** 39 | * 响应消息 40 | */ 41 | private String message; 42 | 43 | /** 44 | * 响应数据 45 | */ 46 | private Map data = new HashMap<>(); 47 | 48 | /** 49 | * 默认私有构造器 50 | */ 51 | private Result(){} 52 | 53 | /** 54 | * 私有自定义构造器 55 | * @param success 响应是否成功 56 | * @param code 响应状态码 57 | * @param message 响应消息 58 | */ 59 | private Result(Boolean success, Integer code, String message){ 60 | this.success = success; 61 | this.code = code; 62 | this.message = message; 63 | } 64 | 65 | /** 66 | * 返回一个默认的 成功操作 的结果,默认响应状态码 200 67 | * @return 成功操作的实例对象 68 | */ 69 | public static Result ok() { 70 | return new Result(true, HttpStatus.SC_OK, "success"); 71 | } 72 | 73 | /** 74 | * 返回一个自定义 成功操作 的结果 75 | * @param success 响应是否成功 76 | * @param code 响应状态码 77 | * @param message 响应消息 78 | * @return 成功操作的实例对象 79 | */ 80 | public static Result ok(Boolean success, Integer code, String message) { 81 | return new Result(success, code, message); 82 | } 83 | 84 | /** 85 | * 返回一个默认的 失败操作 的结果,默认响应状态码为 500 86 | * @return 失败操作的实例对象 87 | */ 88 | public static Result error() { 89 | return new Result(false, HttpStatus.SC_INTERNAL_SERVER_ERROR, "error"); 90 | } 91 | 92 | /** 93 | * 返回一个自定义 失败操作 的结果 94 | * @param success 响应是否成功 95 | * @param code 响应状态码 96 | * @param message 相应消息 97 | * @return 失败操作的实例对象 98 | */ 99 | public static Result error(Boolean success, Integer code, String message) { 100 | return new Result(success, code, message); 101 | } 102 | 103 | /** 104 | * 自定义响应是否成功 105 | * @param success 响应是否成功 106 | * @return 当前实例对象 107 | */ 108 | public Result success(Boolean success) { 109 | this.setSuccess(success); 110 | return this; 111 | } 112 | 113 | /** 114 | * 自定义响应状态码 115 | * @param code 响应状态码 116 | * @return 当前实例对象 117 | */ 118 | public Result code(Integer code) { 119 | this.setCode(code); 120 | return this; 121 | } 122 | 123 | /** 124 | * 自定义响应消息 125 | * @param message 响应消息 126 | * @return 当前实例对象 127 | */ 128 | public Result message(String message) { 129 | this.setMessage(message); 130 | return this; 131 | } 132 | 133 | /** 134 | * 自定义响应数据,一次设置一个 map 集合 135 | * @param map 响应数据 136 | * @return 当前实例对象 137 | */ 138 | public Result data(Map map) { 139 | this.data.putAll(map); 140 | return this; 141 | } 142 | 143 | /** 144 | * 通用设置响应数据,一次设置一个 key - value 键值对 145 | * @param key 键 146 | * @param value 数据 147 | * @return 当前实例对象 148 | */ 149 | public Result data(String key, Object value) { 150 | this.data.put(key, value); 151 | return this; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.config; 2 | 3 | import org.springframework.cache.CacheManager; 4 | import org.springframework.cache.annotation.CachingConfigurerSupport; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 9 | import org.springframework.data.redis.cache.RedisCacheManager; 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; 11 | import org.springframework.data.redis.core.*; 12 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 13 | import org.springframework.data.redis.serializer.RedisSerializationContext; 14 | import org.springframework.data.redis.serializer.StringRedisSerializer; 15 | 16 | import java.time.Duration; 17 | 18 | @EnableCaching 19 | @Configuration 20 | public class RedisConfig extends CachingConfigurerSupport { 21 | 22 | /** 23 | * 配置 redisTemplate 24 | */ 25 | @Bean(name = "redisTemplate") 26 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 27 | // 实例化一个 redisTemplate 28 | RedisTemplate redisTemplate = new RedisTemplate<>(); 29 | // 配置连接工厂 30 | redisTemplate.setConnectionFactory(factory); 31 | // 设置 key 序列化方式 32 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 33 | // 设置 value 序列化方式 34 | redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); 35 | // 设置 hash key 序列化方式 36 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 37 | // 设置 hash value 序列化方式 38 | redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); 39 | return redisTemplate; 40 | } 41 | 42 | /** 43 | * 配置缓存 44 | */ 45 | @Bean 46 | public CacheManager cacheManager(RedisConnectionFactory factory) { 47 | // 定义缓存 key 前缀,默认为 :: 48 | final String keyPrefix = ":"; 49 | // 缓存对象配置 50 | RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() 51 | // 设置缓存默认超时时间:600 秒 52 | .entryTtl(Duration.ofSeconds(600)) 53 | // 设置 key 序列化器 54 | .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) 55 | // 设置 value 序列化器 56 | .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) 57 | // 如果该值为 null,则不缓存 58 | .computePrefixWith(cacheName -> cacheName + keyPrefix) 59 | .disableCachingNullValues(); 60 | // 根据 redis 缓存配置生成 redis 缓存管理器 61 | RedisCacheManager redisCacheManager = RedisCacheManager.builder(factory) 62 | .cacheDefaults(config) 63 | .transactionAware() 64 | .build(); 65 | return redisCacheManager; 66 | } 67 | 68 | /** 69 | * 用于操作 hash 70 | */ 71 | @Bean 72 | public HashOperations hashOperations(RedisTemplate redisTemplate) { 73 | return redisTemplate.opsForHash(); 74 | } 75 | 76 | /** 77 | * 用于操作 list 78 | */ 79 | @Bean 80 | public ListOperations listOperations(RedisTemplate redisTemplate) { 81 | return redisTemplate.opsForList(); 82 | } 83 | 84 | /** 85 | * 用于操作 set 86 | */ 87 | @Bean 88 | public SetOperations setOperations(RedisTemplate redisTemplate) { 89 | return redisTemplate.opsForSet(); 90 | } 91 | 92 | /** 93 | * 用于操作 zset 94 | */ 95 | @Bean 96 | public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { 97 | return redisTemplate.opsForZSet(); 98 | } 99 | 100 | /** 101 | * 用于操作 String 102 | */ 103 | @Bean 104 | public ValueOperations valueOperations(RedisTemplate redisTemplate) { 105 | return redisTemplate.opsForValue(); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /front/src/components/common/Login.vue: -------------------------------------------------------------------------------- 1 | 27 | 123 | 163 | -------------------------------------------------------------------------------- /front/src/views/home/Tab.vue: -------------------------------------------------------------------------------- 1 | 35 | 129 | 152 | -------------------------------------------------------------------------------- /back/src/main/resources/static/sql/sys/sys_role_menu.sql: -------------------------------------------------------------------------------- 1 | -- DROP DATABASE IF EXISTS admin_template; 2 | -- 3 | -- CREATE DATABASE admin_template; 4 | 5 | -- --------------------------sys_role_menu 系统角色菜单表--------------------------------------- 6 | USE admin_template; 7 | DROP TABLE IF EXISTS sys_role_menu; 8 | -- 系统角色菜单表 9 | CREATE TABLE sys_role_menu ( 10 | id bigint NOT NULL COMMENT '角色菜单表 ID', 11 | role_id bigint NOT NULL COMMENT '角色 ID', 12 | menu_id varchar(20) NOT NULL COMMENT '菜单 ID', 13 | create_time datetime DEFAULT NULL COMMENT '创建时间', 14 | update_time datetime DEFAULT NULL COMMENT '修改时间', 15 | delete_flag tinyint DEFAULT NULL COMMENT '逻辑删除标志,0 表示未删除, 1 表示删除', 16 | PRIMARY KEY(id) 17 | ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系统角色菜单表'; 18 | 19 | 20 | -- 插入数据 21 | INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`, `create_time`, `update_time`, `delete_flag`) 22 | VALUES (1278601251755461111, '1278601251755451245', '1278601251755451111', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 23 | (1278601251755461112, '1278601251755451245', '1278601251755452211', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 24 | (1278601251755461113, '1278601251755451245', '1278601251755453311', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 25 | (1278601251755461114, '1278601251755451245', '1278601251755454411', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 26 | (1278601251755461115, '1278601251755451245', '1278601251755452221', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 27 | (1278601251755461116, '1278601251755451245', '1278601251755452231', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 28 | (1278601251755461117, '1278601251755451245', '1278601251755452241', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 29 | (1278601251755461118, '1278601251755451245', '1278601251755452251', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 30 | (1278601251755461119, '1278601251755451245', '1278601251755453321', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 31 | (1278601251755461120, '1278601251755451245', '1278601251755453331', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 32 | (1278601251755461121, '1278601251755451245', '1278601251755453341', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 33 | (1278601251755461122, '1278601251755451245', '1278601251755453351', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 34 | (1278601251755461123, '1278601251755451245', '1278601251755454421', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 35 | (1278601251755461124, '1278601251755451245', '1278601251755454431', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 36 | (1278601251755461125, '1278601251755451245', '1278601251755454441', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 37 | (1278601251755461126, '1278601251755451245', '1278601251755454451', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 38 | (1278601251755461127, '1278601251755451245', '1278601251755455511', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 39 | (1278601251755461128, '1278601251755451245', '1278601251755455521', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 40 | (1278601251755461129, '1278601251755451245', '1278601251755455531', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 41 | 42 | (1278601251755462111, '1278601251755452551', '1278601251755451111', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 43 | (1278601251755462112, '1278601251755452551', '1278601251755452211', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 44 | (1278601251755462113, '1278601251755452551', '1278601251755453311', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 45 | (1278601251755462114, '1278601251755452551', '1278601251755454411', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 46 | (1278601251755462115, '1278601251755452551', '1278601251755452251', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 47 | (1278601251755462116, '1278601251755452551', '1278601251755453351', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 48 | (1278601251755462117, '1278601251755452551', '1278601251755454451', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 49 | (1278601251755462118, '1278601251755452551', '1278601251755455511', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 50 | (1278601251755462119, '1278601251755452551', '1278601251755455521', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 51 | (1278601251755462120, '1278601251755452551', '1278601251755455531', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 52 | 53 | (1278601251755463111, '1278601251755458779', '1278601251755455511', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 54 | (1278601251755463112, '1278601251755458779', '1278601251755455521', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0), 55 | (1278601251755463113, '1278601251755458779', '1278601251755455531', '2020-07-02 16:07:48', '2020-07-02 16:07:48', 0); 56 | 57 | -- --------------------------sys_role_menu 系统角色菜单表--------------------------------------- 58 | -------------------------------------------------------------------------------- /front/src/views/home/Header.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 127 | 128 | 189 | -------------------------------------------------------------------------------- /front/src/views/home/Aside.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 123 | 124 | 165 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.lyh.admin_template.back.common.exception.GlobalException; 6 | import com.lyh.admin_template.back.common.utils.ExceptionUtil; 7 | import com.lyh.admin_template.back.common.utils.MessageSourceUtil; 8 | import com.lyh.admin_template.back.common.utils.Result; 9 | import com.lyh.admin_template.back.common.validator.group.AddGroup; 10 | import com.lyh.admin_template.back.common.validator.group.UpdateGroup; 11 | import com.lyh.admin_template.back.entity.User; 12 | import com.lyh.admin_template.back.service.UserService; 13 | import io.swagger.annotations.Api; 14 | import io.swagger.annotations.ApiOperation; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.cache.annotation.CacheEvict; 17 | import org.springframework.cache.annotation.CachePut; 18 | import org.springframework.cache.annotation.Cacheable; 19 | import org.springframework.data.redis.core.ValueOperations; 20 | import org.springframework.validation.annotation.Validated; 21 | import org.springframework.web.bind.annotation.*; 22 | 23 | import javax.annotation.Resource; 24 | import java.util.HashMap; 25 | 26 | /** 27 | * 用于测试环境搭建各个功能是否成功 28 | */ 29 | @RestController 30 | @RequestMapping("/test") 31 | @Api(tags = "测试页面") 32 | public class TestController { 33 | 34 | @Autowired 35 | private UserService userService; 36 | @Autowired 37 | private MessageSourceUtil messageSourceUtil; 38 | @Resource 39 | private ValueOperations valueOperations; 40 | 41 | @Cacheable(value = "list", key = "'userList'") 42 | @ApiOperation(value = "测试 Redis 缓存注解 @Cacheable") 43 | @GetMapping("/testRedis/cacheable") 44 | public Result testRedisCacheable() { 45 | return Result.ok().data("item", userService.list()); 46 | } 47 | 48 | @CachePut(value = "list", key = "'userList2'") 49 | @ApiOperation(value = "测试 Redis 缓存注解 @CachePut") 50 | @GetMapping("/testRedis/cachePut") 51 | public Result testRedisCachePut() { 52 | return Result.ok().data("item", userService.list()); 53 | } 54 | 55 | @CacheEvict(value = "list", key = "'userList'") 56 | // @CacheEvict(value = "list", key = "'userList'", allEntries=true) 57 | @ApiOperation(value = "测试 Redis 缓存注解 @CacheEvict") 58 | @GetMapping("/testRedis/cacheEvict") 59 | public Result testRedisCacheEvict() { 60 | return Result.ok().data("item", userService.list()); 61 | } 62 | 63 | @ApiOperation(value = "测试国际化返回数据") 64 | @GetMapping("/testLocale") 65 | public Result testI18n() { 66 | return Result.ok().data("test", messageSourceUtil.getMessage("test")); 67 | } 68 | 69 | @ApiOperation(value = "测试 JSR 303 添加时的校验规则") 70 | @PostMapping("testValidator/save") 71 | public Result testValidatorSave(@Validated({AddGroup.class}) @RequestBody User user) { 72 | if(userService.save(user)) { 73 | return Result.ok().message("数据添加成功"); 74 | } 75 | return Result.error().message("数据添加失败"); 76 | } 77 | 78 | @ApiOperation(value = "测试 JSR 303 更新时的校验规则") 79 | @PostMapping("testValidator/update") 80 | public Result testValidatorUpdate(@Validated({UpdateGroup.class}) @RequestBody User user) { 81 | UpdateWrapper updateWrapper = new UpdateWrapper<>(); 82 | updateWrapper.eq("name", user.getName()); 83 | if(userService.update(user, updateWrapper)) { 84 | return Result.ok().message("数据更新成功"); 85 | } 86 | return Result.error().message("数据更新失败"); 87 | } 88 | 89 | @ApiOperation(value = "测试分页插件") 90 | @GetMapping("/testMyBatisPlus/page/{current}/{size}") 91 | public Result testPage(@PathVariable("current") Long current, @PathVariable("size") Long size) { 92 | Page page = new Page(current, size); 93 | return Result.ok().data("page", userService.page(page, null)); 94 | } 95 | 96 | @ApiOperation(value = "获取所有用户信息") 97 | @GetMapping("/getListOfUser") 98 | public Result testGetListOfUser() { 99 | return Result.ok().data("user", userService.list()); 100 | } 101 | 102 | @ApiOperation(value = "删除指定姓名的用户") 103 | @DeleteMapping("/testMyBatisPlus/LogicDel") 104 | public Result testMyBatisPlusLogicDel(@RequestParam String name) { 105 | HashMap hashMap = new HashMap<>(); 106 | hashMap.put("name", name); 107 | userService.removeByMap(hashMap); 108 | return Result.ok(); 109 | } 110 | 111 | @ApiOperation(value = "测试 MyBatis Plus 自动填充数据功能") 112 | @PostMapping("/testMyBatisPlus/AutoFill") 113 | public Result testMyBatisPlusAutoFill(@RequestBody User user) { 114 | if(userService.save(user)) { 115 | return Result.ok().message("数据添加成功"); 116 | } 117 | return Result.error().message("数据添加失败"); 118 | } 119 | 120 | /** 121 | * 用于测试 Swagger 是否整合成功 122 | * @return 123 | */ 124 | @ApiOperation(value = "测试 Swagger") 125 | @GetMapping("/testSwagger") 126 | public Result testSwagger() { 127 | return Result.ok(); 128 | } 129 | 130 | /** 131 | * 用于测试 异常 是否能被统一处理,并返回指定格式的数据 132 | * @return 133 | */ 134 | @GetMapping("/testGlobalException") 135 | public Result testGlobalException() { 136 | try { 137 | int test = 10 / 0; 138 | } catch (Exception e) { 139 | throw new GlobalException(ExceptionUtil.getMessage(e)); 140 | } 141 | return Result.ok(); 142 | } 143 | 144 | /** 145 | * 用于测试 Result 是否能够正常返回指定格式的数据 146 | * @return 147 | */ 148 | @GetMapping("/testResult") 149 | public Result testResult() { 150 | return Result.ok(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/config/JWTConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.config; 2 | 3 | import com.lyh.admin_template.back.common.utils.GsonUtil; 4 | import com.lyh.admin_template.back.common.utils.JwtUtil; 5 | import com.lyh.admin_template.back.common.utils.RedisUtil; 6 | import com.lyh.admin_template.back.common.utils.Result; 7 | import com.lyh.admin_template.back.modules.sys.vo.JwtVo; 8 | import io.jsonwebtoken.Claims; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.http.HttpStatus; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 16 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 17 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | import java.io.PrintWriter; 23 | import java.util.Date; 24 | 25 | @Slf4j 26 | @Configuration 27 | public class JWTConfig { 28 | 29 | @Autowired 30 | private RedisUtil redisUtil; 31 | 32 | /** 33 | * 定义拦截器,拦截请求。 34 | * 其中: 35 | * addPathPatterns 用于添加需要拦截的请求。 36 | * excludePathPatterns 用于添加不需要拦截的请求。 37 | * 此处: 38 | * 拦截所有请求,但是排除 登录、注册 请求 以及 swagger 请求。 39 | */ 40 | @Bean(name = "JWTInterceptor") 41 | public WebMvcConfigurer JWTInterceptor() { 42 | return new WebMvcConfigurer() { 43 | @Override 44 | public void addInterceptors(InterceptorRegistry registry) { 45 | registry.addInterceptor(new JWTInterceptor()) 46 | // 拦截所有请求 47 | .addPathPatterns("/**") 48 | // 不拦截 登录、注册、忘记密码请求 49 | .excludePathPatterns("/sys/sys-user/login/*", "/sys/sys-user/register") 50 | // 不拦截 swagger 请求 51 | .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"); 52 | } 53 | }; 54 | } 55 | 56 | /** 57 | * 定义一个拦截器,用于拦截请求,并对 JWT 进行验证 58 | */ 59 | class JWTInterceptor extends HandlerInterceptorAdapter { 60 | 61 | /** 62 | * 访问 controller 前被调用 63 | */ 64 | @Override 65 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 66 | // 获取 token(从 header 或者 参数中获取) 67 | String token = request.getHeader("token"); 68 | if (StringUtils.isBlank(token)) { 69 | token = request.getParameter("token"); 70 | } 71 | // 验证 token 是否过期(根据时间戳比较) 72 | if (JwtUtil.checkToken(token)) { 73 | // 刷新 token(过期时间小于 5 分钟,会重新创建 token,否则 使用原 token) 74 | token = refreshToken(token, response); 75 | // 获取 token 中的数据 76 | Claims claims = JwtUtil.getTokenBody(token); 77 | JwtVo jwt = GsonUtil.fromJson(String.valueOf(claims.get("data")), JwtVo.class); 78 | // 获取 redis 中存储的 token 79 | String redisToken = redisUtil.get(String.valueOf(jwt.getId())); 80 | // 当前 token 与 redis 中存储的 token 进行比较 81 | if (StringUtils.isNotEmpty(redisToken)) { 82 | // 获取 redis 中 token 的数据 83 | JwtVo redisJwt = GsonUtil.fromJson(String.valueOf(JwtUtil.getTokenBody(redisToken).get("data")), JwtVo.class); 84 | // 若两者 token 相同,则为同一用户再次访问系统,放行 85 | if (redisToken.equals(token)) { 86 | return true; 87 | } else if (redisJwt.getTime() <= jwt.getTime()){ 88 | // redis 中 token 生成时间戳 小于等于 当前 token 生成时间戳,即当前用户为最新登录者 89 | // redis 保存当前最新的 token,并放行 90 | redisUtil.set(String.valueOf(redisJwt.getId()), token, 60 * 30); 91 | return true; 92 | } 93 | } 94 | } 95 | // 认证失败,返回数据,并返回 401 状态码 96 | returnJsonData(response); 97 | return false; 98 | } 99 | } 100 | 101 | /** 102 | * 返回 json 格式的数据 103 | */ 104 | public void returnJsonData(HttpServletResponse response) { 105 | PrintWriter pw = null; 106 | response.setCharacterEncoding("UTF-8"); 107 | response.setContentType("application/json; charset=utf-8"); 108 | try { 109 | pw = response.getWriter(); 110 | // 返回 code 为 401,表示 token 失效。 111 | pw.print(GsonUtil.toJson(Result.error().message("token 失效或过期").code(HttpStatus.SC_UNAUTHORIZED))); 112 | } catch (IOException e) { 113 | log.error(e.getMessage()); 114 | throw new RuntimeException(e); 115 | } 116 | } 117 | 118 | /** 119 | * 刷新 token 120 | * @param token 旧 token 值 121 | * @return 新 token 122 | */ 123 | public String refreshToken(String token, HttpServletResponse response) { 124 | // 获取 token 中的数据 125 | Claims claims = JwtUtil.getTokenBody(token); 126 | // 获取 token 过期时间戳 127 | Long expire = claims.getExpiration().getTime(); 128 | // 获取当前时间戳 129 | Long currentTime = new Date().getTime(); 130 | // 若当前 token 过期时间 小于 5 分钟,则 更新 token 131 | if ((expire - currentTime) / 1000 < 300) { 132 | // 获取 token 数据 133 | JwtVo jwt = GsonUtil.fromJson(String.valueOf(claims.get("data")), JwtVo.class); 134 | // 重新生成 token 135 | token = JwtUtil.getJwtToken(jwt, 1000L * 60 * 30); 136 | // 把新 token 保存在 redis 中 137 | redisUtil.set(String.valueOf(jwt.getId()), token, 60 * 30); 138 | response.setHeader("token", token); 139 | } 140 | return token; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /back/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.0.RELEASE 9 | 10 | 11 | com.lyh.admin_template 12 | back 13 | 0.0.1-SNAPSHOT 14 | back 15 | 后台管理系统模板 -- 后台代码 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | 24 | io.jsonwebtoken 25 | jjwt 26 | 0.9.0 27 | 28 | 29 | 30 | 31 | com.aliyun 32 | aliyun-java-sdk-core 33 | 4.5.1 34 | 35 | 36 | 37 | 38 | com.aliyun.oss 39 | aliyun-sdk-oss 40 | 3.8.0 41 | 42 | 43 | 44 | 45 | com.google.code.gson 46 | gson 47 | 2.8.6 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-mail 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-data-redis 60 | 61 | 62 | 63 | org.apache.commons 64 | commons-pool2 65 | 2.6.0 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-devtools 71 | runtime 72 | 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-validation 78 | 79 | 80 | 81 | 82 | com.baomidou 83 | mybatis-plus-boot-starter 84 | 3.3.1 85 | 86 | 87 | 88 | com.baomidou 89 | mybatis-plus-generator 90 | 3.3.1.tmp 91 | 92 | 93 | 94 | org.apache.velocity 95 | velocity-engine-core 96 | 2.2 97 | 98 | 99 | 100 | mysql 101 | mysql-connector-java 102 | 8.0.18 103 | 104 | 105 | 106 | 107 | io.springfox 108 | springfox-swagger2 109 | 2.9.2 110 | 111 | 112 | 113 | io.springfox 114 | springfox-swagger-ui 115 | 2.9.2 116 | 117 | 118 | 119 | 120 | org.apache.httpcomponents 121 | httpcore 122 | 4.4.13 123 | 124 | 125 | 126 | org.projectlombok 127 | lombok 128 | 1.18.12 129 | provided 130 | 131 | 132 | org.springframework.boot 133 | spring-boot-starter-web 134 | 135 | 136 | 137 | org.springframework.boot 138 | spring-boot-starter-test 139 | test 140 | 141 | 142 | org.junit.vintage 143 | junit-vintage-engine 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | org.springframework.boot 153 | spring-boot-maven-plugin 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /back/src/main/resources/static/html/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 30 | 33 | 点击上传 34 | 35 |
只能上传jpg/png/gif文件,且不超过 5 MB
36 |
37 | 下载 38 |
39 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/common/utils/OssUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.common.utils; 2 | 3 | import com.aliyun.oss.OSS; 4 | import com.aliyun.oss.OSSClientBuilder; 5 | import com.aliyun.oss.common.utils.BinaryUtil; 6 | import com.aliyun.oss.model.MatchMode; 7 | import com.aliyun.oss.model.PolicyConditions; 8 | import lombok.Data; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.io.ByteArrayInputStream; 14 | import java.io.InputStream; 15 | import java.net.URL; 16 | import java.nio.charset.StandardCharsets; 17 | import java.time.LocalDateTime; 18 | import java.time.format.DateTimeFormatter; 19 | import java.util.Date; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.UUID; 23 | 24 | /** 25 | * Oss 工具类,用于操作 OSS 26 | */ 27 | @Data 28 | @Component 29 | public class OssUtil { 30 | @Value("${aliyun.endPoint}") 31 | private String endPoint; 32 | @Value("${aliyun.bucketName}") 33 | private String bucketName; 34 | @Value("${aliyun.accessKeyId}") 35 | private String accessKeyId; 36 | @Value("${aliyun.accessKeySecret}") 37 | private String accessKeySecret; 38 | @Value("${aliyun.domain}") 39 | private String domain; 40 | 41 | /** 42 | * 设置文件上传路径(prefix + 日期 + uuid + suffix) 43 | */ 44 | public String getPath(String prefix, String suffix) { 45 | // 生成 UUID 46 | String uuid = UUID.randomUUID().toString().replaceAll("-", ""); 47 | // 格式化日期 48 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd"); 49 | // 拼接文件路径 50 | String path = dateTimeFormatter.format(LocalDateTime.now()) + "/" + uuid; 51 | if (StringUtils.isNotEmpty(prefix)) { 52 | path = prefix + "/" + path; 53 | } 54 | return path + "-" + suffix; 55 | } 56 | 57 | /** 58 | * 上传文件 59 | */ 60 | public String upload(byte[] data, String path) { 61 | return upload(new ByteArrayInputStream(data), path); 62 | } 63 | 64 | /** 65 | * 上传文件,自定义 前后缀 66 | */ 67 | public String uploadSuffix(byte[] data, String prefix ,String suffix) { 68 | return upload(data, getPath(prefix, suffix)); 69 | } 70 | 71 | /** 72 | * 上传文件,自定义 前后缀 73 | */ 74 | public String uploadSuffix(InputStream inputStream, String prefix, String suffix) { 75 | return upload(inputStream, getPath(prefix, suffix)); 76 | } 77 | 78 | /** 79 | * 上传文件 80 | */ 81 | public String upload(InputStream inputStream, String path) { 82 | try { 83 | // 创建 OSSClient 实例。 84 | OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); 85 | // 上传文件到 指定 bucket 86 | ossClient.putObject(bucketName, path, inputStream); 87 | // 关闭 OSSClient 88 | ossClient.shutdown(); 89 | } catch (Exception e) { 90 | throw new RuntimeException("上传文件失败"); 91 | } 92 | return path; 93 | } 94 | 95 | /** 96 | * 获取文件 url 97 | */ 98 | public String getUrl(String key) { 99 | // 用于保存 url 地址 100 | URL url = null; 101 | try { 102 | // 创建 OSSClient 实例。 103 | OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); 104 | // 设置 url 过期时间(10 年) 105 | Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 24 * 365 * 10); 106 | // 获取 url 地址 107 | url = ossClient.generatePresignedUrl(bucketName, key, expiration); 108 | // 关闭 OSSClient 109 | ossClient.shutdown(); 110 | } catch (Exception e) { 111 | throw new RuntimeException("获取文件 url 失败"); 112 | } 113 | return url != null ? url.toString() : null; 114 | } 115 | 116 | /** 117 | * 用于获取签名数据 118 | */ 119 | public Map getPolicy() { 120 | return getPolicy(getPath("aliyun", "signature")); 121 | } 122 | 123 | /** 124 | * 用于获取签名数据,用于服务端直传文件到服务器 125 | */ 126 | public Map getPolicy(String path) { 127 | // 用于保存 128 | Map map = new HashMap<>(); 129 | try { 130 | // 创建 OSSClient 实例。 131 | OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); 132 | // 用于设置 post 上传条件 133 | PolicyConditions policyConditions = new PolicyConditions(); 134 | // 设置最大上传文件大小(1G) 135 | policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); 136 | // 设置文件前缀 137 | policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, path); 138 | // 设置签名过期时间(6 小时) 139 | Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 6); 140 | // 生成 policy 141 | String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions); 142 | // 设置编码字符集(UTF-8) 143 | byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8); 144 | // 设置加密格式(Base64) 145 | String encodedPolicy = BinaryUtil.toBase64String(binaryData); 146 | // 计算签名 147 | String postSignature = ossClient.calculatePostSignature(postPolicy); 148 | // 封装数据 149 | map.put("ossaccessKeyId", accessKeyId); 150 | map.put("policy", encodedPolicy); 151 | map.put("signature", postSignature); 152 | map.put("key", path); 153 | map.put("expire", String.valueOf(expiration.getTime() / 1000)); 154 | map.put("host", domain); 155 | // 关闭 OSSClient 156 | ossClient.shutdown(); 157 | } catch (Exception e) { 158 | throw new RuntimeException("获取签名数据失败"); 159 | } 160 | return map; 161 | } 162 | 163 | /** 164 | * 删除 OOS 中的文件 165 | */ 166 | public void deleteObject(String objectName) { 167 | try { 168 | // 创建 OSSClient 实例。 169 | OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); 170 | 171 | // 删除指定 bucket 中的文件 172 | ossClient.deleteObject(bucketName, objectName); 173 | 174 | // 关闭 OSSClient 175 | ossClient.shutdown(); 176 | } catch (Exception e) { 177 | throw new RuntimeException("删除文件失败"); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /front/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | import { 5 | getToken 6 | } from '@/http/auth.js' 7 | import http from '@/http/http.js' 8 | import { 9 | isURL, 10 | isDynamicRoutes 11 | } from '@/utils/validate.js' 12 | 13 | Vue.use(VueRouter) 14 | 15 | // 定义路由跳转规则 16 | // component 采用 路由懒加载形式 17 | // 此项目中,均采用 name 方式指定路由进行跳转 18 | /* 19 | meta 用于定义路由元信息, 20 | 其中 21 | isRouter 用于指示是否开启路由守卫(true 表示开启)。 22 | isTab 用于表示是否显示为标签页(true 表示显示) 23 | iframeUrl 用于表示 url,使用 http 或者 https 开头的 url 使用 iframe 标签展示 24 | */ 25 | const routes = [{ 26 | path: '/', 27 | redirect: { 28 | name: "Login" 29 | } 30 | }, 31 | { 32 | path: '/404', 33 | name: '404', 34 | component: () => import('@/components/common/404.vue') 35 | }, 36 | { 37 | path: '/Login', 38 | name: 'Login', 39 | component: () => import('@/components/common/Login.vue') 40 | }, 41 | { 42 | path: '/Home', 43 | name: 'Home', 44 | component: () => import('@/views/Home.vue'), 45 | redirect: { 46 | name: 'HomePage' 47 | }, 48 | children: [{ 49 | path: '/Home/Page', 50 | name: 'HomePage', 51 | component: () => import('@/views/menu/HomePage.vue'), 52 | meta: { 53 | isRouter: true 54 | } 55 | }, 56 | { 57 | path: '/Home/Demo/Echarts', 58 | name: 'Echarts', 59 | component: () => import('@/views/menu/Echarts.vue'), 60 | meta: { 61 | isRouter: true, 62 | isTab: true 63 | } 64 | }, 65 | { 66 | path: '/Home/Demo/Ueditor', 67 | name: 'Ueditor', 68 | component: () => import('@/views/menu/Ueditor.vue'), 69 | meta: { 70 | isRouter: true, 71 | isTab: true 72 | } 73 | }, 74 | { 75 | path: '/Home/Demo/Baidu', 76 | name: 'Baidu', 77 | meta: { 78 | isRouter: true, 79 | isTab: true, 80 | iframeUrl: '@/test.html' 81 | } 82 | }, 83 | // 路由匹配失败时,跳转到 404 页面 84 | { 85 | path: "*", 86 | redirect: { 87 | name: '404' 88 | } 89 | } 90 | ] 91 | } 92 | ] 93 | 94 | // 创建一个 router 实例 95 | const router = new VueRouter({ 96 | // routes 用于定义 路由跳转 规则 97 | routes, 98 | // mode 用于去除地址中的 # 99 | mode: 'history', 100 | // scrollBehavior 用于定义路由切换时,页面滚动。 101 | scrollBehavior: () => ({ 102 | y: 0 103 | }), 104 | // isAddDynamicMenuRoutes 表示是否已经添加过动态菜单(防止频繁请求动态菜单) 105 | isAddDynamicMenuRoutes: false 106 | }) 107 | 108 | // 添加全局路由导航守卫 109 | router.beforeEach((to, from, next) => { 110 | // 当开启导航守卫时,验证 token 是否存在。 111 | // to.meta.isRouter 表示是否开启动态路由 112 | // isDynamicRoutes 判断该路由是否为动态路由(页面刷新时,动态路由没有 isRouter 值,此时 to.meta.isRouter 条件不成立,即动态路由拼接逻辑不能执行) 113 | if (to.meta.isRouter || isDynamicRoutes(to.path)) { 114 | // console.log(router) 115 | // 获取 token 值 116 | let token = getToken() 117 | // token 不存在时,跳转到 登录页面 118 | if (!token || !/\S/.test(token)) { 119 | next({ 120 | name: 'Login' 121 | }) 122 | } 123 | // token 存在时,判断是否已经获取过 动态菜单,未获取,即 false 时,需要获取 124 | if (!router.options.isAddDynamicMenuRoutes) { 125 | http.menu.getMenus().then((response => { 126 | // 数据返回成功时 127 | if (response && response.data.code === 200) { 128 | // 设置动态菜单为 true,表示不用再次获取 129 | router.options.isAddDynamicMenuRoutes = true 130 | // 获取动态菜单数据 131 | let results = fnAddDynamicMenuRoutes(response.data.data) 132 | // 如果动态菜单数据存在,对其进行处理 133 | if (results && results.length > 0) { 134 | // 遍历第一层数据 135 | results.map(value => { 136 | // 如果 path 值不存在,则对其赋值,并指定 component 为 Home.vue 137 | if (!value.path) { 138 | value.path = `/DynamicRoutes-${value.meta.menuId}` 139 | value.name = `DynamicHome-${value.meta.menuId}` 140 | value.component = () => import('@/views/Home.vue') 141 | } 142 | }) 143 | } 144 | // 使用 vuex 管理动态路由数据 145 | router.app.$options.store.dispatch('common/updateDynamicRoutes', results) 146 | // 使用 router 实例的 addRoutes 方法,给当前 router 实例添加一个动态路由 147 | router.addRoutes(results) 148 | } 149 | })) 150 | } 151 | } 152 | next() 153 | }) 154 | 155 | // 用于处理动态菜单数据,将其转为 route 形式 156 | function fnAddDynamicMenuRoutes(menuList = [], routes = []) { 157 | // 用于保存普通路由数据 158 | let temp = [] 159 | // 用于保存存在子路由的路由数据 160 | let route = [] 161 | // 遍历数据 162 | for (let i = 0; i < menuList.length; i++) { 163 | // 存在子路由,则递归遍历,并返回数据作为 children 保存 164 | if (menuList[i].subMenuList && menuList[i].subMenuList.length > 0) { 165 | // 获取路由的基本格式 166 | route = getRoute(menuList[i]) 167 | // 递归处理子路由数据,并返回,将其作为路由的 children 保存 168 | route.children = fnAddDynamicMenuRoutes(menuList[i].subMenuList) 169 | // 保存存在子路由的路由 170 | routes.push(route) 171 | } else { 172 | // 保存普通路由 173 | temp.push(getRoute(menuList[i])) 174 | } 175 | } 176 | // 返回路由结果 177 | return routes.concat(temp) 178 | } 179 | 180 | // 返回路由的基本格式 181 | function getRoute(item) { 182 | // 路由基本格式 183 | let route = { 184 | // 路由的路径 185 | path: '', 186 | // 路由名 187 | name: '', 188 | // 路由所在组件 189 | component: null, 190 | meta: { 191 | // 开启路由守卫标志 192 | isRouter: true, 193 | // 开启标签页显示标志 194 | isTab: true, 195 | // iframe 标签指向的地址(数据以 http 或者 https 开头时,使用 iframe 标签显示) 196 | iframeUrl: '', 197 | // 开启动态路由标志 198 | isDynamic: true, 199 | // 动态菜单名称(nameZH 显示中文, nameEN 显示英文) 200 | name_zh: item.name_zh, 201 | name_en: item.name_en, 202 | // 动态菜单项的图标 203 | icon: item.icon, 204 | // 菜单项的 ID 205 | menuId: item.menuId, 206 | // 菜单项的父菜单 ID 207 | parentId: item.parentId, 208 | // 菜单项排序依据 209 | orderNum: item.orderNum, 210 | // 菜单项类型(0: 目录,1: 菜单项,2: 按钮) 211 | type: item.type 212 | }, 213 | // 路由的子路由 214 | children: [] 215 | } 216 | // 如果存在 url,则根据 url 进行相关处理(判断是 iframe 类型还是 普通类型) 217 | if (item.url && /\S/.test(item.url)) { 218 | // 若 url 有前缀 /,则将其去除,方便后续操作。 219 | item.url = item.url.replace(/^\//, '') 220 | // 定义基本路由规则,将 / 使用 - 代替 221 | route.path = item.url.replace('/', '-') 222 | route.name = item.url.replace('/', '-') 223 | // 如果是 外部 url,使用 iframe 标签展示,不用指定 component,重新指定 path、name 以及 iframeUrl。 224 | if (isURL(item.url)) { 225 | route['path'] = `iframe-${item.menuId}` 226 | route['name'] = `iframe-${item.menuId}` 227 | route['meta']['iframeUrl'] = item.url 228 | } else { 229 | // 如果是项目模块,使用 route-view 标签展示,需要指定 component(加载指定目录下的 vue 组件) 230 | route.component = () => import(`@/views/dynamic/${item.url}.vue`) || null 231 | } 232 | } 233 | // 返回 route 234 | return route 235 | } 236 | 237 | // 解决相同路径跳转报错 238 | const routerPush = VueRouter.prototype.push; 239 | VueRouter.prototype.push = function push(location, onResolve, onReject) { 240 | if (onResolve || onReject) 241 | return routerPush.call(this, location, onResolve, onReject) 242 | return routerPush.call(this, location).catch(error => error) 243 | } 244 | 245 | export default router 246 | -------------------------------------------------------------------------------- /back/src/test/java/com/lyh/admin_template/back/TestRedis.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back; 2 | 3 | import com.lyh.admin_template.back.common.utils.RedisUtil; 4 | import lombok.Data; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.data.redis.core.DefaultTypedTuple; 9 | import org.springframework.data.redis.core.ZSetOperations; 10 | import org.springframework.util.CollectionUtils; 11 | 12 | import java.util.*; 13 | 14 | @SpringBootTest 15 | public class TestRedis { 16 | 17 | @Autowired 18 | private RedisUtil redisUtil; 19 | 20 | /** 21 | * 测试 Redis common 操作 22 | */ 23 | @Test 24 | void testCommon() { 25 | redisUtil.set("hello", "10", 100); 26 | System.out.println("所有的 key: " + redisUtil.keys("*")); 27 | System.out.println("key 中是否存在 hello: " + redisUtil.hasKey("hello")); 28 | System.out.println("获取 hello 的过期时间: " + redisUtil.getExpire("hello")); 29 | redisUtil.del("hello"); 30 | System.out.println("获取 hello 的过期时间: " + redisUtil.getExpire("hello")); 31 | } 32 | 33 | /** 34 | * 测试 Redis string 操作 35 | */ 36 | @Test 37 | void testString() { 38 | System.out.println("所有的 key: " + redisUtil.keys("*")); 39 | redisUtil.set("hello", "10", 100); 40 | System.out.println(redisUtil.get("hello")); 41 | System.out.println(redisUtil.incr("hello",100)); 42 | System.out.println(redisUtil.get("hello")); 43 | } 44 | 45 | /** 46 | * 测试 Redis hash 操作 47 | */ 48 | @Test 49 | void testHash() { 50 | List lists = new ArrayList<>(); 51 | lists.add(new Student("tom")); 52 | lists.add(new Student("jarry")); 53 | Teacher teacher = new Teacher(); 54 | teacher.setName("teacher"); 55 | teacher.setStudents(lists); 56 | 57 | Map map = new HashMap<>(); 58 | map.put("tom", 12); 59 | map.put("jarry", 24); 60 | 61 | redisUtil.hset("hash", "hello", teacher); 62 | redisUtil.hmset("hash", map, 100); 63 | 64 | System.out.println(redisUtil.hkeys("hash")); 65 | System.out.println(redisUtil.hHasKey("hash", "hello")); 66 | System.out.println(redisUtil.hget("hash", "hello")); 67 | System.out.println(redisUtil.hgetAll("hash")); 68 | System.out.println(redisUtil.hmget("hash")); 69 | System.out.println(redisUtil.getExpire("hash")); 70 | 71 | redisUtil.del("hash", "hello", "hello2"); 72 | System.out.println(redisUtil.hkeys("hash")); 73 | } 74 | 75 | /** 76 | * 测试 Redis list 操作 77 | */ 78 | @Test 79 | void testList() { 80 | redisUtil.lpush("list", 1); 81 | redisUtil.rpush("list", 2); 82 | redisUtil.lpush("list", CollectionUtils.arrayToList(new int[]{3, 4, 5})); 83 | redisUtil.rpush("list", CollectionUtils.arrayToList(new int[]{6, 7, 8})); 84 | 85 | System.out.println(redisUtil.llen("list")); 86 | System.out.println(redisUtil.lrange("list", 0, -1)); 87 | 88 | redisUtil.lsetIndex("list", 2, 10); 89 | System.out.println(redisUtil.lgetIndex("list", 2)); 90 | 91 | System.out.println(redisUtil.lrange("list", 0, -1)); 92 | System.out.println(redisUtil.lremove("list", 10, 2)); 93 | System.out.println(redisUtil.lrange("list", 0, -1)); 94 | 95 | redisUtil.ltrim("list", 0, 3); 96 | System.out.println(redisUtil.lrange("list", 0, -1)); 97 | 98 | System.out.println(redisUtil.lpop("list")); 99 | System.out.println(redisUtil.rpop("list")); 100 | System.out.println(redisUtil.lrange("list", 0, -1)); 101 | 102 | redisUtil.del("list"); 103 | } 104 | 105 | /** 106 | * 测试 Redis set 操作 107 | */ 108 | @Test 109 | void testSet() { 110 | redisUtil.sset("set", "1"); 111 | redisUtil.sset("set", "1", "2", "3"); 112 | System.out.println(redisUtil.smembers("set")); 113 | System.out.println(redisUtil.sisMember("set", "2")); 114 | System.out.println(redisUtil.slen("set")); 115 | 116 | System.out.println(redisUtil.srandomMember("set", 10)); 117 | System.out.println(redisUtil.smembers("set")); 118 | 119 | System.out.println(redisUtil.spop("set")); 120 | System.out.println(redisUtil.smembers("set")); 121 | 122 | redisUtil.del("set"); 123 | } 124 | 125 | /** 126 | * 测试 Redis zset 操作 127 | */ 128 | @Test 129 | void testZset() { 130 | redisUtil.zset("zset", "20", 1); 131 | redisUtil.zset("zset", "10", 1); 132 | redisUtil.zset("zset", "30", 2); 133 | System.out.println(redisUtil.zrange("zset", 0, -1)); 134 | System.out.println(redisUtil.zrangeByScore("zset", 0, 1)); 135 | 136 | System.out.println(redisUtil.zreverseRange("zset", 0, -1)); 137 | System.out.println(redisUtil.zreverseRangeByScore("zset", 0, 1)); 138 | 139 | System.out.println(redisUtil.zlen("zset")); 140 | System.out.println(redisUtil.zlenByScore("zset", 0, 1)); 141 | 142 | System.out.println(redisUtil.zscore("zset", "20")); 143 | 144 | // System.out.println(redisUtil.zremove("zset", "20", "30", "40")); 145 | // System.out.println(redisUtil.zremoveRange("zset", 1, 4)); 146 | System.out.println(redisUtil.zremoveByScore("zset", 2, 4)); 147 | System.out.println(redisUtil.zrange("zset", 0, -1)); 148 | 149 | redisUtil.zset("zset", new Student("tom"), 10); 150 | redisUtil.zset("zset", new Student("jarry"), 20); 151 | System.out.println(redisUtil.zrange("zset", 0, -1)); 152 | System.out.println(redisUtil.zrank("zset", new Student("tom"))); 153 | System.out.println(redisUtil.zreverseRange("zset", 0, -1)); 154 | System.out.println(redisUtil.zreverseRank("zset", new Student("tom"))); 155 | 156 | Set> set = new HashSet<>(); 157 | set.add(new DefaultTypedTuple("20", 1.0)); 158 | set.add(new DefaultTypedTuple("10", 1.0)); 159 | set.add(new DefaultTypedTuple("30", 2.0)); 160 | redisUtil.zset("zset", set); 161 | redisUtil.zrangeWithScores("zset", 0, -1).forEach((item) -> { 162 | System.out.println(item.getValue() + "===" + item.getScore()); 163 | }); 164 | 165 | redisUtil.zreverseRangeWithScores("zset", 0, -1).forEach((item) -> { 166 | System.out.println(item.getValue() + "===" + item.getScore()); 167 | }); 168 | 169 | redisUtil.del("zset"); 170 | } 171 | } 172 | @Data 173 | class Teacher { 174 | private List students; 175 | private String name; 176 | } 177 | 178 | @Data 179 | class Student { 180 | private String name; 181 | Student() { 182 | 183 | } 184 | Student(String name) { 185 | this.name = name; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /back/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | logback 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | DEBUG 32 | 33 | 34 | ${CONSOLE_LOG_PATTERN} 35 | 36 | UTF-8 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ${log.path}/debug.log 45 | 46 | 47 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 48 | UTF-8 49 | 50 | 51 | 52 | 53 | ${log.path}/debug-%d{yyyy-MM-dd}.%i.log 54 | 55 | 100MB 56 | 57 | 58 | 15 59 | 60 | 61 | 62 | debug 63 | ACCEPT 64 | DENY 65 | 66 | 67 | 68 | 69 | 70 | 71 | ${log.path}/info.log 72 | 73 | 74 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 75 | UTF-8 76 | 77 | 78 | 79 | 80 | ${log.path}/info-%d{yyyy-MM-dd}.%i.log 81 | 82 | 100MB 83 | 84 | 85 | 15 86 | 87 | 88 | 89 | info 90 | ACCEPT 91 | DENY 92 | 93 | 94 | 95 | 96 | 97 | 98 | ${log.path}/warn.log 99 | 100 | 101 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 102 | UTF-8 103 | 104 | 105 | 106 | ${log.path}/warn-%d{yyyy-MM-dd}.%i.log 107 | 108 | 100MB 109 | 110 | 111 | 15 112 | 113 | 114 | 115 | warn 116 | ACCEPT 117 | DENY 118 | 119 | 120 | 121 | 122 | 123 | 124 | ${log.path}/error.log 125 | 126 | 127 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 128 | UTF-8 129 | 130 | 131 | 132 | ${log.path}/error-%d{yyyy-MM-dd}.%i.log 133 | 134 | 100MB 135 | 136 | 137 | 15 138 | 139 | 140 | 141 | ERROR 142 | ACCEPT 143 | DENY 144 | 145 | 146 | 147 | 148 | 149 | 150 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /back/src/main/java/com/lyh/admin_template/back/modules/sys/controller/SysUserController.java: -------------------------------------------------------------------------------- 1 | package com.lyh.admin_template.back.modules.sys.controller; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.lyh.admin_template.back.common.utils.*; 6 | import com.lyh.admin_template.back.common.validator.group.sys.LoginGroup; 7 | import com.lyh.admin_template.back.common.validator.group.sys.RegisterGroup; 8 | import com.lyh.admin_template.back.modules.sys.entity.SysUser; 9 | import com.lyh.admin_template.back.modules.sys.service.SysUserService; 10 | import com.lyh.admin_template.back.modules.sys.vo.*; 11 | import io.swagger.annotations.Api; 12 | import io.swagger.annotations.ApiOperation; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.apache.http.HttpStatus; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.validation.annotation.Validated; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.util.Date; 20 | 21 | /** 22 | *

23 | * 系统用户表 前端控制器 24 | *

25 | * 26 | * @author lyh 27 | * @since 2020-07-02 28 | */ 29 | @RestController 30 | @RequestMapping("/sys/sys-user") 31 | @Api(tags = "用户登录、注册操作") 32 | public class SysUserController { 33 | 34 | /** 35 | * 用于操作 sys_user 表 36 | */ 37 | @Autowired 38 | private SysUserService sysUserService; 39 | /** 40 | * 用于操作 redis 41 | */ 42 | @Autowired 43 | private RedisUtil redisUtil; 44 | /** 45 | * 用于操作 短信验证码发送 46 | */ 47 | @Autowired 48 | private SmsUtil smsUtil; 49 | /** 50 | * 常量,表示用户密码登录操作 51 | */ 52 | private static final String USER_NAME_STATUS = "0"; 53 | /** 54 | * 常量,表示手机号密码登录操作 55 | */ 56 | private static final String PHONE_STATUS = "1"; 57 | 58 | /** 59 | * 获取 jwt 60 | * @return jwt 61 | */ 62 | private String getJwt(SysUser sysUser) { 63 | // 获取需要保存在 jwt 中的数据 64 | JwtVo jwtVo = new JwtVo(); 65 | jwtVo.setId(sysUser.getId()); 66 | jwtVo.setName(sysUser.getName()); 67 | jwtVo.setPhone(sysUser.getMobile()); 68 | jwtVo.setTime(new Date().getTime()); 69 | // 获取 jwt 数据,设置过期时间为 30 分钟 70 | String jwt = JwtUtil.getJwtToken(jwtVo, 1000L * 60 * 30); 71 | // 判断用户是否重复登录(code 有值则重复登录,需要保留最新的登录者,剔除前一个登录者) 72 | String code = redisUtil.get(String.valueOf(sysUser.getId())); 73 | // 获取当前时间戳 74 | Long currentTime = new Date().getTime(); 75 | // 如果 redis 中存在 jwt 数据,则根据时间戳比较谁为最新的登陆者 76 | if (StringUtils.isNotEmpty(code)) { 77 | // 获取 redis 中存储的 jwt 数据 78 | JwtVo redisJwt = GsonUtil.fromJson(String.valueOf(JwtUtil.getTokenBody(code).get("data")), JwtVo.class); 79 | // redis jwt 大于 当前时间戳,则 redis 中 jwt 为最新登录者,当前登录失败 80 | if (redisJwt.getTime() > currentTime) { 81 | return null; 82 | } 83 | } 84 | // 把数据存放在 redis 中,设置过期时间为 30 分钟 85 | redisUtil.set(String.valueOf(sysUser.getId()), jwt, 60 * 30); 86 | return jwt; 87 | } 88 | 89 | /** 90 | * 使用密码进行真实登录操作 91 | * @param account 账号(用户名或手机号) 92 | * @param pwd 密码 93 | * @param status 是否使用用户名登录(0 表示用户名登录,1 表示手机号登录) 94 | * @return jwt 95 | */ 96 | private String pwdLogin(String account, String pwd, String status) { 97 | // 新增查询条件 98 | QueryWrapper queryWrapper = new QueryWrapper(); 99 | // 如果是用户名 + 密码登录,则根据 姓名 + 密码 查找数据 100 | if (USER_NAME_STATUS.equals(status)) { 101 | queryWrapper.eq("name", account); 102 | } 103 | // 如果是手机号 + 密码登录,则根据 手机号 + 密码 查找数据 104 | if (PHONE_STATUS.equals(status)) { 105 | queryWrapper.eq("mobile", account); 106 | } 107 | // 添加密码条件,密码进行 MD5 加密后再与数据库数据比较 108 | queryWrapper.eq("password", MD5Util.encrypt(pwd)); 109 | // 获取用户数据 110 | SysUser sysUser = sysUserService.getOne(queryWrapper); 111 | // 如果存在用户数据 112 | if (sysUser != null) { 113 | return getJwt(sysUser); 114 | } 115 | return null; 116 | } 117 | 118 | /** 119 | * 使用 验证码进行真实登录操作 120 | * @param phone 手机号 121 | * @param code 验证码 122 | * @return jwt 123 | */ 124 | private String codeLogin(String phone, String code) { 125 | // 获取 redis 中存放的验证码 126 | String redisCode = redisUtil.get(phone); 127 | // 存在验证码,且输入的验证码与 redis 存放的验证码相同,则根据手机号去数据库查询数据 128 | if (StringUtils.isNotEmpty(redisCode) && code.equals(redisCode)) { 129 | // 新增查询条件 130 | QueryWrapper queryWrapper = new QueryWrapper(); 131 | // 根据手机号去查询数据 132 | queryWrapper.eq("mobile", phone); 133 | SysUser sysUser = sysUserService.getOne(queryWrapper); 134 | // 如果存在用户数据 135 | if (sysUser != null) { 136 | return getJwt(sysUser); 137 | } 138 | } 139 | return null; 140 | } 141 | 142 | @ApiOperation(value = "使用用户名、密码登录") 143 | @PostMapping("/login/namePwdLogin") 144 | public Result namePwdLogin(@Validated({LoginGroup.class}) @RequestBody NamePwdLoginVo namePwdLoginVo) { 145 | String jwt = pwdLogin(namePwdLoginVo.getUserName(), namePwdLoginVo.getPassword(), USER_NAME_STATUS); 146 | if (StringUtils.isNotEmpty(jwt)) { 147 | return Result.ok().message("登录成功").data("token", jwt); 148 | } 149 | return Result.error().message("登录失败").code(HttpStatus.SC_UNAUTHORIZED); 150 | } 151 | 152 | @ApiOperation(value = "使用手机号、密码登录") 153 | @PostMapping("/login/phonePwdLogin") 154 | public Result phonePwdLogin(@Validated({LoginGroup.class}) @RequestBody PhonePwdLoginVo phonePwdLoginVo) { 155 | String jwt = pwdLogin(phonePwdLoginVo.getPhone(), phonePwdLoginVo.getPassword(), PHONE_STATUS); 156 | if (StringUtils.isNotEmpty(jwt)) { 157 | return Result.ok().message("登录成功").data("token", jwt); 158 | } 159 | return Result.error().message("登录失败").code(HttpStatus.SC_UNAUTHORIZED); 160 | } 161 | 162 | @ApiOperation(value = "使用手机号、验证码登录") 163 | @PostMapping("/login/phoneCodeLogin") 164 | public Result phoneCodeLogin(@Validated({LoginGroup.class}) @RequestBody PhoneCodeLoginVo phoneCodeLoginVo) { 165 | String jwt = codeLogin(phoneCodeLoginVo.getPhone(), phoneCodeLoginVo.getCode()); 166 | if (StringUtils.isNotEmpty(jwt)) { 167 | return Result.ok().message("登录成功").data("token", jwt); 168 | } 169 | return Result.error().message("登录失败").code(HttpStatus.SC_UNAUTHORIZED); 170 | } 171 | 172 | @ApiOperation(value = "获取短信验证码") 173 | @GetMapping("/login/getCode") 174 | public Result getCode(String phone) { 175 | // 设置默认过期时间 176 | Long defaultTime = 60L * 5; 177 | // 先判断 redis 中是否存储过验证码(设置期限为 1 分钟),防止重复获取验证码 178 | Long expire = redisUtil.getExpire(phone); 179 | if (expire != null && (defaultTime - expire < 60)) { 180 | return Result.error().message("验证码已发送,1 分钟后可再次获取验证码"); 181 | } else { 182 | // 获取 短信验证码 183 | String code = smsUtil.sendSms(phone); 184 | if (StringUtils.isNotEmpty(code)) { 185 | // 把验证码存放在 redis 中,并设置 过期时间 为 5 分钟 186 | redisUtil.set(phone, code, defaultTime); 187 | return Result.ok().message("验证码获取成功").data("code", code); 188 | } 189 | } 190 | return Result.error().message("验证码获取失败"); 191 | } 192 | 193 | @ApiOperation(value = "用户登出") 194 | @GetMapping("/logout") 195 | public Result logout(@RequestParam String userName) { 196 | // 先获取用户数据 197 | QueryWrapper queryWrapper = new QueryWrapper(); 198 | queryWrapper.eq("name", userName); 199 | SysUser sysUser = sysUserService.getOne(queryWrapper); 200 | // 用户存在时 201 | if (sysUser != null) { 202 | // 生成并返回一个无效的 token 203 | String jwt = JwtUtil.getJwtToken(null, 1000L); 204 | // 删除 redis 中的 token 205 | redisUtil.del(String.valueOf(sysUser.getId())); 206 | return Result.ok().message("登出成功").data("token", jwt); 207 | } 208 | return Result.error().message("登出失败"); 209 | } 210 | 211 | @ApiOperation(value = "用户注册") 212 | @PostMapping("/register") 213 | public Result register(@Validated({RegisterGroup.class}) @RequestBody RegisterVo registerVo) { 214 | if (save(registerVo)) { 215 | return Result.ok().message("用户注册成功"); 216 | } 217 | return Result.error().message("用户注册失败").code(HttpStatus.SC_UNAUTHORIZED); 218 | } 219 | 220 | /** 221 | * 真实注册操作 222 | * @param registerVo 注册数据 223 | * @return true 为插入成功, false 为失败 224 | */ 225 | public boolean save(RegisterVo registerVo) { 226 | // 判断 redis 中是否存在 验证码 227 | String code = redisUtil.get(registerVo.getPhone()); 228 | // redis 中存在验证码且与当前验证码相同 229 | if (StringUtils.isNotEmpty(code) && code.equals(registerVo.getCode())) { 230 | SysUser sysUser = new SysUser(); 231 | sysUser.setName(registerVo.getUserName()).setPassword(MD5Util.encrypt(registerVo.getPassword())); 232 | sysUser.setMobile(registerVo.getPhone()); 233 | return sysUserService.saveUser(sysUser); 234 | } 235 | return false; 236 | } 237 | 238 | @ApiOperation(value = "测试刷新 token") 239 | @GetMapping("/test") 240 | public Result testRefreshToken() { 241 | return Result.ok(); 242 | } 243 | } 244 | 245 | --------------------------------------------------------------------------------