1.权限系统
1.前端使用: vue + elementui + axios + css + html
2.后端使用: springboot+mybatis-plus +mybatis+druid+shiro+swagger2+redis
2. 登录界面
2.1前端布局
2.1.1创建Login.vue
2.1.2在Login.vue中写入登录的代码
<template>
<!--这里必须使用一个双标签-->
<div id="login_box" >
<div class="img_position">
<el-avatar :size="140" :src="imgUrl"></el-avatar>
</div>
<el-card class="box-card">
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="用户名" prop="name">
<el-input type="text" v-model="ruleForm.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary"
size="mini"
:plain="true"
@click="login"
style="margin-left: 100px;width: 100px">登录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
2.1.3登录界面的方法、数据
<script>
export default {
name: "Login",
data(){
return{
ruleForm: {
name: '',
password: ''
},
rules: {
name: [
{required: true, message:'用户名不能为空', trigger: 'blur'},
],
password: [
{required: true, message: '密码不能为空', trigger: 'blur'},
]
},
imgUrl:'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202010%2F16%2F20201016234725_52ed2.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1662528624&t=c2ac363744d360cd4d61b7a9ac9cffef'
}
},
methods:{
login(){
this.$refs['ruleForm'].validate((valid) => {
if(valid) {
this.$message.success("登录成功")
} else {
this.$message.error("登录失败")
}
})
}
}
}
2.1.4 样式
<style>
#login_box{
position: relative;
width: 500px;
margin: 250px auto;
background-image: url("../assets/2.jpeg");
}
#login_box div.img_position{
position: absolute;
left: 200px;
top: -70px;
}
.text {
font-size: 14px;
}
.item {
padding: 18px 0;
}
.box-card {
padding: 100px 50px 0 0;
width: 480px;
}
</style>
2.1.5路由的渲染
2.1.6路由的设置
2.1.7页面的跳转
2.1.8登录按钮事件
如果想在vue工程中使用axios进行异步请求,则需要在main.js中导入axios
[1]//导入axios
import axios from "axios";
[2]//把axios挂载到vue对象中,以后在vue中如果使用axios直接可以用$http名称
Vue.prototype.$http=axios
登录方法
methods:{
login(){
//表单校验
this.$refs['ruleForm'].validate((valid) => {
if(valid){
//url:后端登录接口的路径
this.$http.post("http://localhost:8808/user/login",this.ruleForm).then(result=>{
});
}
})
}
}
2.1.9 浏览器打开的登录界面
2.2后端登录接口
2.2.1新建Springboot工程
2.2.2导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ykq</groupId>
<artifactId>qy151-springboot-vue</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>qy151-springboot-vue</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2.3MybatisPlus代码生成器
package com.ppp;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
/**
* TODO
*
* @author ppp
* @version 1.0
* @since 2022-07-25 19:07:17
*/
public class Generator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/shiropermission?serverTimezone=Asia/Shanghai", "root", "root")
.globalConfig(builder -> {
builder.author("ppp") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir(".\\src\\main\\java\\"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.ppp") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "src\\main\\resources\\mapper\\")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("acl_role_permission","acl_user","acl_role","acl_permission")// 设置需要生成的表名
.addTablePrefix("acl_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
2.2.4配置Application文件
server.port=8809
spring.datasource.druid.url=jdbc:mysql://localhost:3306/shiropermission?serverTimezone=Asia/Shanghai
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.username=root
spring.datasource.druid.password=root
#日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2.2.5测试登录接口
2.2.6前端调用后端登录接口
为跨域问题:
当使用异步请求从一个网址访问另一个网址时可能会出现跨域问题。
前提:
1. 必须为异步请求
2. 当端口号或协议或ip不同时则会出现跨域
如何解决跨域:
后端解决---->这里也有几种方式:
【1】可以借助nginx.
【2】在代码中解决
在控制层接口上添加@CrossOrigin
(origins = {"192.168.0.111:8080","192.168.0.120:8081"},allowedHeaders="运行哪些请求头跨域",methods={"GET","POST"})
origins: 允许哪些域可以跨域访问我这个接口
allowedHeaders:允许哪些请求头信息跨域
methods: 允许哪些请求方式跨域
上面再控制层接口处加上注解的方式解决跨,麻烦的地方就需要对每个控制类都加该注解。 设置一个全局跨域配置类。
注意:加完配置类记得把接口上的注解去掉
登录成功后前端路由跳转
2.2.7使用swaager测试接口
(1)配置swaager的工具类
@Configuration
public class SwaggerConfig {
@Bean
public Docket docket(){
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.psh.system.controller"))
.build();
return docket;
}
public ApiInfo apiInfo(){
Contact DEFAULT_CONTAT=new Contact("psh","http://www.aaa.com","111@qq.com");
ApiInfo apiInfo= new ApiInfo("qy151在线文档","这是一个文档","A1.0","http://www.aaa.com",
DEFAULT_CONTAT,"AAA","http://www.aaa.com",new ArrayList<VendorExtension>());
return apiInfo;
}
}
(2)在主启动类上开启swaager注解
建议使用这个版本这个比较稳定
要用redis的原因是因为如果高并发访问数据库数据库会崩溃,造成系统瘫痪
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application配置文件
#redis服务器的配置
spring.redis.host=localhost
spring.redis.port=6379
RedisTemplate类需要序列化,加入一个配置类
@Configuration
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化 filed value
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(redisSerializer);
return template;
}
}
2.3.1修改后端登录的接口
@RestController
@RequestMapping("system")
@Api(tags = "登录接口类")
public class LoginController {
@Autowired
private IUserService userService;
@Autowired
private RedisTemplate redisTemplate;
@PostMapping("login")
@ApiOperation(value="登录接口")
public CommonResult login(@RequestBody LoginVo loginVo){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username",loginVo.getName());
wrapper.eq("password",loginVo.getPassword());
wrapper.eq("is_deleted",0);
User one = userService.getOne(wrapper);
if(one!=null){
//随机生成一个唯一字符串。
String token = UUID.randomUUID().toString();
//把该token作为redis的key value为当前登录用户信息
ValueOperations forValue = redisTemplate.opsForValue();
forValue.set(token,one,24, TimeUnit.HOURS);
return new CommonResult(2000,"登录成功",token);
}else{
return new CommonResult(5000,"登录失败",null);
}
}
}
2.3.2修改前端的登录方法
由于每次前端都往后端请求都得要人为添加参数token. 我们可以使用axios得请求拦截器
2.3.3 axios请求拦截器
//设置axios得请求拦截器---在请求头上添加token
axios.interceptors.request.use(config=>{
//从sessionStorage获取token值
var token = sessionStorage.getItem("token");
if(token){ //token不为空则为真
//请求头中会携带token
config.headers.token=token;
}
return config;
})
2.3.3验证token有没有被使用
前端方法:
<template>
<div>
<el-button type="primary" @click="getInfo">获取用户信息</el-button>
</div>
</template>
<script>
export default {
name: "User",
methods:{
getInfo(){
this.$http.get("http://localhost:8081/system/user/getInfo").then(result=>{
console.log(result)
})
}
}
}
</script>
<style scoped>
</style>
后端接口:
@RestController
@RequestMapping("/system/user")
@Api(tags = "用户接口类")
public class UserController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("getInfo")
public CommonResult getInfo(HttpServletRequest request){
String token = request.getHeader("token");
System.out.println(token);
//根据token从redis中获取用户信息
ValueOperations forValue = redisTemplate.opsForValue();
User o = (User) forValue.get(token);
return new CommonResult(2000,"获取用户信息成功",o);
}
}
3 前置路由守卫
前置路由守卫:就是在路由跳转前加上自己得一些业务代码,在main.js中配置。类似于拦截器
//设置前置路由守卫 to:到哪个路由 from:从哪个路由来 next():放行到指定路由
router.beforeEach((to,from,next)=>{
//获取跳转得路径
var path = to.path;
//判断是否为登录路由路径
if(path==="/login"){
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
//放行
return next();
}
//其他路由路径 判断是否登录过
var token = sessionStorage.getItem("token");
if(token){
return next();
}
//跳转登录
return next("/login");
})
4. 整合shiro
4.1添加shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
4.2shiro的配置类
@Configuration //交于容器管理
public class ShiroConfig {
//安全认证
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(realm());
return securityManager;
}
//安全数据源 自定义realm
@Bean
public Realm realm(){
MyRealm myRealm=new MyRealm();
myRealm.setCredentialsMatcher(credentialsMatcher());
return myRealm;
}
//密码匹配器
@Bean
public CredentialsMatcher credentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
@Autowired
private RedisTemplate redisTemplate;
//shiro过滤器工厂 设置过滤的规则
@Bean(value = "shiroFilter")
public ShiroFilterFactoryBean filterFactoryBean(){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager());
//设置拦截规则
HashMap<String,String> map=new HashMap<>();
map.put("/system/login","anon");
map.put("/doc.html","anon");
map.put("/swagger-ui.html","anon");
map.put("/swagger/**","anon");
map.put("/webjars/**", "anon");
map.put("/swagger-resources/**","anon");
map.put("/v2/**","anon");
map.put("/static/**", "anon");
map.put("/**","authc");
factoryBean.setFilterChainDefinitionMap(map);
//设置自定义认证过滤器
HashMap<String,Filter> filterMap=new HashMap<String, Filter>();
filterMap.put("authc",new LoginFilter(redisTemplate));
factoryBean.setFilters(filterMap);
return factoryBean;
}
//注册filter
@Bean
public FilterRegistrationBean<Filter> filterRegistrationBean(){
FilterRegistrationBean<Filter> filterRegistrationBean=new FilterRegistrationBean<>();
filterRegistrationBean.setName("shiroFilter");
filterRegistrationBean.setFilter(new DelegatingFilterProxy());
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
//开始shiro注解
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
}
4.3增加一个realm类对象
public class MyRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username",username);
wrapper.eq("is_deleted",0);
User user = userService.getOne(wrapper);
if(user!=null){
ByteSource bytes = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),bytes,this.getName());
return info;
}
return null;
}
}
4.4修改controller代码
@RestController
@RequestMapping("system")
@Api(tags = "登录接口类")
public class LoginController {
@Autowired
private IUserService userService;
@Autowired
private RedisTemplate redisTemplate;
@PostMapping("login")
@ApiOperation(value="登录接口")
public CommonResult login(@RequestBody LoginVo loginVo){
try{
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginVo.getName(), loginVo.getPassword(),loginVo.getSalt());
subject.login(usernamePasswordToken);
Object one = subject.getPrincipal();
String token = UUID.randomUUID().toString();
ValueOperations forValue = redisTemplate.opsForValue();
forValue.set(token,one,24,TimeUnit.HOURS);
return new CommonResult(2000,"登录成功",token);
}catch (Exception e){
return new CommonResult(5000,"登录失败",null);
}
}
}
5.主页布局
<template >
<el-container>
<el-header>
<div id="logo" style="display: inline-block;width:50%;height: 100%;float: left" >
<img src="../assets/1.jpg" height="100%">
</div>
<span id="avatar" style="float: right">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link" style="margin-top: 10px; display: inline-block;">
<el-avatar ></el-avatar>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="info">个人信息</el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</el-header>
<el-container>
<el-aside width="200px">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
background-color="darkgrey"
text-color="#fff"
:router="true"
unique-opened="unique-opened"
active-text-color="#ffd04b">
</el-menu>
</el-aside>
<el-main>
<router-view/>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
name: "User",
methods:{
getInfo(){
this.$http.get("http://localhost:8809/system/user/getInfo").then(result=>{
console.log(result)
})
}
}
}
</script>
<style>
html, body, #app {
height: 100%;
}
body, #app {
padding: 0px;
margin: 0px;
}
.el-container {
height: 100%;
}
.el-header, .el-footer {
background-color: grey;
color: #333;
line-height: 60px;
}
.el-aside {
background-color: darkgrey;
color: #333;
line-height: 560px;
}
.el-aside > .el-menu {
border: none;
}
.el-main {
background-color: lightgrey;
color: darkolivegreen;
}
body > .el-container {
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
line-height: 320px;
}
</style>
5.1退出
5.1.1前端:
由于每个跳转都需要写ip和端口,我们可以在main.js中设置axios的基础路径
//设置axios基础路径 axios.defaults.baseURL="http://localhost:8808"
5.1.2后端接口
@GetMapping("logout")
@ApiOperation(value = "退出接口")
public CommonResult logout(HttpServletRequest request){
String token = request.getHeader("token");
if (redisTemplate.hasKey(token)) {
redisTemplate.delete(token);
return new CommonResult(2000, "退出成功", null);
}
return new CommonResult(5000,"退出失败",null);
}
6.查询左侧菜单
6.1前端方法
initLeftMenu(){
this.$http.get("/system/permission/leftMenu").then(result=>{
if(result.data.code===2000){
this.leftMenus=result.data.data;
}
})
},
6.2后端
6.2.1Controller层
远程仓库
java:
https://gitee.com/panshih/springly.git
vue: