熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离

别迷路,先收藏!!!

代码获取

关注我公众号,汪程序员

b站教程

https://www.bilibili.com/video/BV1XT411u7Dk/

sql

回复“熊猫电影sql”

代码

https://github.com/WangWenShuai529/FilmSystem
给个start!!!

代码也可以通过公众号回复“熊猫电影代码”。

https://download.csdn.net/download/m0_61504367/85913163

打一个广告

人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站
https://www.captainai.net/shuai

熊猫后台管理系统前端界面(panda-ui)

跨域

main.js

import Vue from 'vue'
import './plugins/axios'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
// 导入字体图标
import './assets/css/fonts/iconfont.css'
import {Message} from 'element-ui'
import has from './assets/js/permission'
import global_variable from "@/components/global_variable";
// import echarts from 'echarts'
// Vue.prototype.$echarts = echarts

Vue.prototype.global = global_variable

//默认的跨域url,后端的端口号为8181
axios.defaults.baseURL = 'http://127.0.0.1:8181/'
axios.interceptors.request.use(config => {
  config.headers.Token = window.sessionStorage.getItem('token')
  return config
})

// 状态码错误信息
const codeMessage = {
  401: '用户没有权限,请登录',
  403: '用户没有权限,请联系管理员',
  404: '访问的资源不存在',
  406: '请求的格式不可得',
  410: '请求的资源被永久删除,且不会再得到的',
  422: '当创建一个对象时,发生一个验证错误',
  500: '服务器发生错误,请检查服务器',
  502: '网关错误',
  503: '服务不可用,服务器暂时过载或维护',
  504: '网关超时'
};

axios.interceptors.response.use(response => {
  return response
}, error => {
  if(error){
    if(!error.response){
      return console.log('Error', error.message);
    }
    //获取状态码
    const status = error.response.status;
    const errortext = codeMessage[status] || error.response.statusText;

    //提示错误信息
    Message.error(errortext)

    // 错误状态处理
    if (status === 401) {
      router.push('/login')
    } else if (status === 403) {
      router.push('/login')
    } else if (status >= 404 && status < 422) {
      router.push('/404')
    }
  }
  return Promise.reject(error)
})

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

后端也需要接收跨域的请求,这里在后面实现

登录功能

运行截图

login.vue实现

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第1张图片
login.vue

<template>
  <div class="login_container">
    <div class="login_box">
      <div class="title_box">
        <p>熊猫影院管理登录</p>
      </div>
      <!-- 登录表单区域 -->
<!--   loginForm双向绑定   -->
      <el-form class="login_form" :model="loginForm" :rules="loginFormRules" ref="loginFormRef">
        <!-- 用户名 -->
        <el-form-item prop="userName">
          <el-input v-model="loginForm.userName" placeholder="请输入用户名" clearable
                    prefix-icon="iconfont icon-user"></el-input>
        </el-form-item>
        <!-- 密码 -->
        <el-form-item prop="password">
          <el-input v-model="loginForm.password" type="password" placeholder="请输入密码" show-password
                    prefix-icon="iconfont icon-lock"></el-input>
        </el-form-item>
        <!-- 按扭区域 -->
        <el-form-item class="btns">
          <el-button size="medium" :round="true" type="primary" @click="login">点击登录</el-button>
          <el-button size="medium" :round="true" type="info" @click="resetLoginForm">恢复默认</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script>
export default {
  name: "Login",
  data() {
    return {
      //登录表单数据对象
      //设立初始值,方便调试
      loginForm: {
        userName: 'admin',
        password: '123456'
      },
      //表单验证规则
      loginFormRules: {
        //验证用户名
        userName: [
          { required: true, message: "请输入用户名称", trigger: "blur"},
          { min:2, max: 20, message: "长度在2到20个字符之间", trigger: "blur"}
        ],
        //验证密码
        password: [
          { required: true, message: "请输入密码", trigger: "blur"},
          { min:6, max: 16, message: "长度在6到16个字符之间", trigger: "blur"}
        ]
      }
    }
  },
  methods:{
    success(params) {
      console.log(params);
      this.login()
    },
    //点击重置按钮,重置表单
    resetLoginForm(){
      console.log(this.$refs)
      //只是将查询条件初始化,所以在初始化时绑定什么值就还是什么值。用于清空表单
      this.$refs.loginFormRef.resetFields();
    },
    login() {
      this.$refs.loginFormRef.validate(async valid => {
        //用于表单的验证
        if(!valid) return;
        axios.defaults.headers.post['Content-Type'] = 'application/json'
        //JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。
        const { data: res} = await axios.post('sysUser/login', JSON.stringify(this.loginForm));
        //登录失败
        if(res.code !== 200) return this.$message.error(res.msg);
        //控制登录权限
        if(res.data.sysUser.sysRole.children === null || res.data.sysUser.sysRole.children[0] === null) {
          this.$message.error("抱歉,您没有权限登录,请联系管理员获取权限")
          return
        }
        this.$message.success("登录成功")
        // console.log(res.data);
        //保存token,放入sessionStorage,浏览器关闭就会取消
        window.sessionStorage.setItem("token", res.data.token)
        window.sessionStorage.setItem("loginUser", JSON.stringify({sysUser : res.data.sysUser, cinemaId : res.data.cinemaId, cinemaName : res.data.cinemaName}));
        // window.sessionStorage.setItem("btnPermission", res.data.sysUser.sysRole.roleId === 1 ? "admin" : "normal")
        window.sessionStorage.setItem("btnPermission", res.data.sysUser.sysRole.roleId === 1 ? "admin" : "admin")
        //导航跳转到首页
        await this.$router.push('/welcome');
      })
    }
  }
}
</script>

<style scoped>
.login_container{
  /*背景图片*/
  background-image: url("../assets/login-background1.jpg");
  height: 100%;
}

.login_box{
  width: 450px;
  height: 300px;
  background-color: #fff;
  border-radius: 3px;
  position: absolute;
  left: 50%;
  top: 50%;
  /*一般用于居中,这个其实就是一个位移的属性,translatex在x轴方向上进行移动,反之translatey实在y轴方向,而translate括号里的两个参数是先x后y的。*/
  transform: translate(-50%, -50%);
}

.avatar_box{
  height: 130px;
  width: 130px;
  border: 1px solid #eee;
  border-radius: 50%;
  padding: 10px;
  box-shadow: 0 0 10px #ddd;
  position: absolute;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: #fff;
}

.avatar_box > img{
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background-color: #eee;
}

.title_box{
  text-align: center;
  font-size: 200%;
}

.login_form{
  position: absolute;
  bottom: 0;
  width: 100%;
  padding: 0 20px;
  box-sizing: border-box;
}

.btns{
  display: flex;
  justify-content: center;
}
</style>

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第2张图片
把请求的数据放进session中:
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第3张图片
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第4张图片
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第5张图片

Home.vue
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第6张图片

影院信息管理

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第7张图片
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第8张图片

界面初次加载,从后端拿到的数据
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第9张图片

加载后端的图片地址:
在这里插入图片描述

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第10张图片
代码

<template>
  <div>
    <!--面包屑导航区域-->
    <div class="board">
      <el-breadcrumb separator-class="el-icon-arrow-right">
        <el-breadcrumb-item :to="{ path: '/welcome' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item>熊猫影院管理</el-breadcrumb-item>
        <el-breadcrumb-item>熊猫影院信息管理</el-breadcrumb-item>
      </el-breadcrumb>
    </div>

    <!--卡片视图-->
    <el-card class="box-card">
      <!--表格显示影院信息-->
      <el-form :model="cinemaInfo" label-width="150px">
        <el-form-item label="公司名称: " prop="cinemaName">
<!--          显示cinemaInfo的信息-->
          <el-input class="el-input-show" v-model="cinemaInfo.cinemaName" disabled></el-input>
        </el-form-item>
        <el-form-item label="公司地址: " prop="cinemaAddress">
          <el-input class="el-input-show" v-model="cinemaInfo.cinemaAddress" disabled></el-input>
        </el-form-item>
        <el-form-item label="营业电话: " prop="cinemaPhone">
          <el-input class="el-input-show" v-model="cinemaInfo.cinemaPhone" disabled></el-input>
        </el-form-item>
        <el-form-item label="营业时间: " prop="cinemaPhone">
          <el-input class="el-input-show-time" v-model="cinemaInfo.workStartTime" disabled></el-input><el-input class="el-input-show-time" v-model="cinemaInfo.workEndTime" disabled></el-input>
        </el-form-item>
        <el-form-item label="拥有电影类型: " prop="hallCategory">
          <el-tag v-for="hall in halls" >{{hall}}</el-tag>
        </el-form-item>
        <el-form-item label="影院图片: ">
          <span v-for="item in pics">
            <el-popover placement="left" trigger="click" width="300">
              <img :src="item.url" width="200%"/>
<!--              图片后端的地址:["/images/cinema/2020/12/15/123.jpg"]-->
              <img slot="reference" :src="item.url" :alt="item"
                   style="max-height: 300px;max-width: 300px;padding: 10px"/>
            </el-popover>
          </span>
        </el-form-item>
        <el-form-item label="" prop="cinemaName">
          <el-button type="primary" @click="showEditDialog">修改影院信息</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!--修改影院对话框-->
    <el-dialog title="修改影院" :visible.sync="editDialogVisible" width="60%" @close="editDialogClosed">
<!--      绑定editForm-->
      <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="150px">
        <el-form-item label="影院名称" prop="cinemaName">
          <el-input v-model="editForm.cinemaName"></el-input>
        </el-form-item>
        <el-form-item label="影院地址" prop="cinemaAddress">
          <el-input v-model="editForm.cinemaAddress"></el-input>
        </el-form-item>
        <el-form-item label="影院电话" prop="cinemaPhone">
          <el-input v-model="editForm.cinemaPhone"></el-input>
        </el-form-item>
        <el-form-item label="开始营业时间" prop="workStartTime">
          <el-time-picker
            v-model="editForm.workStartTime"
            value-format="HH:mm"
            placeholder="选择开始营业时间">
          </el-time-picker>
        </el-form-item>
        <el-form-item label="结束营业时间" prop="workEndTime">
          <el-time-picker
            v-model="editForm.workEndTime"
            value-format="HH:mm"
            placeholder="选择结束营业时间">
          </el-time-picker>
        </el-form-item>
        <el-form-item label="拥有影厅类型" prop="hallCategoryList">
          <el-input class="el-input-hall" placeholder="请输入添加影厅类别名称" v-model="inputHall" clearable></el-input>
          <el-button type="primary" @click="addHallCategory()">添加</el-button>
        </el-form-item>
        <el-form-item >
          <el-tag
            v-for="(item, index) in halls"
            :key="index"
            closable
            @close="deleteHallCategory(index)">
            {{item}}
          </el-tag>
        </el-form-item>

        <el-form-item label="影院图片">
          <el-upload action="" list-type="picture-card"
                     :auto-upload="false"
                     :file-list="pics"
                     :on-change="handleChange"
                     :on-success="handleSuccess"
                     :on-error="handleError"
                     ref="pictureEditRef"
                     :http-request="submitFile">
            <i slot="default" class="el-icon-plus"></i>
            <div slot="file" slot-scope="{file}">
              <img class="el-upload-list__item-thumbnail"
                   :src="file.url" alt="">
              <span class="el-upload-list__item-actions">
                <span class="el-upload-list__item-preview"
                      @click="handlePictureCardPreview(file)">
                  <i class="el-icon-zoom-in"></i>
                </span>
                <span class="el-upload-list__item-delete"
                      @click="handleRemove(file)">
                  <i class="el-icon-delete"></i>
                </span>
              </span>
            </div>
          </el-upload>
          <!--放大预览-->
          <el-dialog :visible.sync="dialogVisible" append-to-body>
            <img width="100%" :src="dialogImageUrl" alt="">
          </el-dialog>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="cancelEdit">取 消</el-button>
        <el-button type="primary" @click="editCinemaInfo">确 定</el-button>
      </span>
    </el-dialog>


  </div>
</template>

<script>
  import global from "../../assets/css/global.css"
  export default {
    data() {
      return {
        //控制对话框的显示与隐藏(这个是修改的窗口)
        editDialogVisible: false,

        editForm: {},
        cinemaInfo: {},
        //校验规则
        editFormRules: {
          cinemaName: [
            { required: true, message: '请输入影院名称', trigger: 'change' }
          ],
          cinemaAddress: [
            { required: true, message: '请输入影院地址', trigger: 'change' }
          ],
          cinemaPhone: [
            { required: true, message: '请输入影院电话', trigger: 'change' }
          ]
        },
        inputHall: '',

        dialogImageUrl: '',
        dialogVisible: false,
        hideUpload: false,
        //添加删除图片 动态绑定图片列表
        pics: [],
        //添加删除影厅类别 动态绑定影厅列表
        halls: [],
        // 发送给后端的JSON图片数组
        pictureList: [],
        picNums: 0,
        deletePicList: []
      }
    },
    created() {
      this.getCinemaInfo()
    },
    methods: {
      //请求数据
      async getCinemaInfo() {
        const _this = this
        await axios.get('sysCinema').then(resp => {
          _this.cinemaInfo = resp.data.data
        })
        _this.pics = []
        _this.halls = []

        //解开json数组,使用 JSON.parse() 方法将数据转换为 JavaScript 对象。
        for (const item of JSON.parse(this.cinemaInfo.cinemaPicture)) {
          let pic = {}
          pic['name'] = ''
          pic['url'] = this.global.base + item
          this.pics.push(pic)
        }
        for (const item of JSON.parse(this.cinemaInfo.hallCategoryList)) {
          this.halls.push(item)
        }
      },
      // 显示修改对话框,回显数据
      async showEditDialog() {
        //修改表单的赋值
        this.editForm = this.cinemaInfo
        //把false改成true,显示
        this.editDialogVisible = true
      },
      // 监听修改对话框的关闭事件
      editDialogClosed() {
        this.$refs.editFormRef.resetFields()
        this.$refs.pictureEditRef.clearFiles()
        this.pics = []
        this.pictureList = []
        this.halls = []
        //重新获得界面的数据
        this.getCinemaInfo()
      },
      // 取消修改
      cancelEdit() {
        this.editDialogVisible = false
        //把删除的数据移除列表,splice(index) ——> 从index的位置开始,删除之后的所有元素(包括第index个)
        this.deletePicList.splice(0, this.deletePicList.length)
      },
      //异步请求,修改cinemaInfo
      async editCinemaInfo() {
        //提交图片
        await this.submitFile()
        //JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。
        this.editForm.cinemaPicture = JSON.stringify(this.pictureList)
        this.editForm.hallCategoryList = JSON.stringify(this.halls)

        this.$refs.editFormRef.validate(async valid => {
          const _this = this
          if (!valid) return
          let success = true
          axios.defaults.headers.put['Content-Type'] = 'application/json'
          //async 关键字用于函数上(async函数的返回值是Promise实例对象)
          // await 关键字用于 async 函数当中(await可以得到异步的结果)
          await axios.put('sysCinema/update', JSON.stringify(_this.editForm)).then(resp => {
            if (resp.data.code !== 200){
              this.$message.error('修改影院信息失败!')
              success = false
            }
          })
          if (!success) return
          this.editDialogVisible = false
          await this.getCinemaInfo()
          this.$message.success('修改影院信息成功!')
          //删除数据库中的删除图片
          for(let item of this.deletePicList) {
            //substring() 方法用于提取字符串中介于两个指定下标之间的字符。
            //indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
            await axios.get('/upload/delete?filePath=' + item.substring(item.indexOf('/images')))
          }
        })
      },
      //增加类型
      addHallCategory() {
        if (this.inputHall === '' || this.inputHall === null) {
          this.$alert('影厅类别添加失败!原因:所添加的影厅类别不能为空。', '影厅类别添加异常', {
            confirmButtonText: '我知道了'
          })
          return
        } else if (!this.halls.includes(this.inputHall)) {
          this.halls.push(this.inputHall)
        } else {
          console.log('已存在')
          this.$alert('影厅类别添加失败!原因:所添加的影厅类别已存在。', '影厅类别添加异常', {
            confirmButtonText: '我知道了'
          })
        }
        this.inputHall = ''
      },
      //删除类型
      deleteHallCategory(index) {
        this.halls.splice(index, 1)
        console.log(this.halls)
      },
      //删除图片
      handleRemove(file, fileList) {
        const filePath = file.url
        console.log(filePath)
        const idx = this.pics.findIndex(x => x.url === filePath)
        if (file.status === 'success') {
          this.deletePicList.push(file.url)
        }
        this.pics.splice(idx, 1)
      },
      //展示图片
      handlePictureCardPreview(file) {
        this.dialogImageUrl = file.url
        this.dialogVisible = true
      },
      handleChange(file, filelist){
        //slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
        this.pics = filelist.slice(0)
      },
      handleSuccess(response){
        this.pictureList.push(response.data)
        this.editForm = JSON.stringify(this.pictureList)
      },
      handleError(err){
        console.log(err)
      },
      //提交文件
      async submitFile() {
        const _this = this
        for (let i = 0; i < this.pics.length; i++){
          let formData = new FormData()
          if (this.pics[i].status === 'success') {
            let s = this.pics[i].url
            //加入到pictureList
            this.pictureList.push(s.substring(s.indexOf('/images')))
            //      显示:   /images/cinema/2020/12/15/123.jpg
            // console.log(s.substring(s.indexOf('/images')));
            continue
          }
          let file = this.pics[i].raw
          formData.append('file', file)
          await axios.post('upload/cinema', formData).then(response =>{
            _this.pictureList.push(response.data.data)
          })
        }
      }
    }
  }
</script>

<style scoped>
.el-tag{
  margin: 0 10px 10px 0;
}
.row{
  white-space: nowrap;
  margin-top: 10px;
  padding: 0 10px;
  text-align: center;
  display: flex;
  justify-content: space-between;
}

.row2{
  margin-top: 20px;
}
.el-input-show{
  width: 420px;
}
.el-input-show-time{
  width: 100px;
}
.el-input-hall {
  width: 240px;
  margin: 0 20px 0px 0;
}
</style>

电影信息管理

运行截图


熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第11张图片

初始化

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第12张图片

实现

选择时间,利用的DateTimePicker 日期时间选择器
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第13张图片
注意:添加、修改电影信息对话框,代码大同小异。
一个绑定的是addForm,另外一个是editForm。
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第14张图片

电影类别管理

运行截图

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第15张图片
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第16张图片
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第17张图片

实现

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第18张图片
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第19张图片

<template>
  <div>
    <!--面包屑导航类别-->
    <div class="board">
      <el-breadcrumb separator-class="el-icon-arrow-right">
        <el-breadcrumb-item :to="{ path: '/welcome' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item>电影管理</el-breadcrumb-item>
        <el-breadcrumb-item>电影类别</el-breadcrumb-item>
      </el-breadcrumb>
    </div>


    <!--卡片视图-->
    <el-card class="box-card">
      <el-row :gutter="20">
        <el-col :span="2">
          <el-button type="primary" @click="addDialogVisible = true">添加类别</el-button>
        </el-col>
        <el-col :span="2">
          <el-button type="danger" @click="multipleDelete">批量删除类别</el-button>
        </el-col>
      </el-row>

      <!--类别分类列表-->
      <el-table :data="movieCategoryList" style="width: 45%" border stripe @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column prop="movieCategoryId" label="类别编号" width="145"></el-table-column>
        <el-table-column prop="movieCategoryName" label="电影类别" width="180"></el-table-column>
        <el-table-column label="操作" width="150">
          <template slot-scope="scope">
            <el-tooltip effect="dark" content="修改电影类别" placement="top" :enterable="false" :open-delay="500">
<!--             showEditDialog(scope.row.movieCategoryId),这一行的ID-->
              <el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog(scope.row.movieCategoryId)"></el-button>
            </el-tooltip>
            <el-tooltip effect="dark" content="删除类别" placement="top" :enterable="false" :open-delay="500">
              <el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteMovieCategoryById(scope.row.movieCategoryId)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>

      <!--分页类别-->
      <el-pagination
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :current-page="queryInfo.pageNum"
          :page-sizes="[5, 7, 9]"
          :page-size="queryInfo.pageSize"
          layout="total, sizes, prev, pager, next, jumper"
          :total="total">
      </el-pagination>
    </el-card>

    <!--添加类别对话框-->
    <el-dialog title="添加类别" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
      <!--内容主题区-->
      <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px">
        <!--prop:在addFormRules中定义校验规则, v-model:双向绑定数据-->
        <el-form-item label="电影类别" prop="movieCategoryName">
          <el-input v-model="addForm.movieCategoryName"></el-input>
        </el-form-item>
      </el-form>
      <!--底部区域-->
      <span slot="footer" class="dialog-footer">
      <el-button @click="addDialogVisible = false">取 消</el-button>
      <el-button type="primary" @click="addMovieCategory">确 定</el-button>
      </span>
    </el-dialog>

    <!--修改类别对话框-->
    <el-dialog title="修改类别" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed">
      <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px">
        <el-form-item label="类别编号">
          <el-input v-model="editForm.movieCategoryId" disabled></el-input>
        </el-form-item>
        <el-form-item label="电影类别" prop="movieCategoryName">
          <el-input v-model="editForm.movieCategoryName"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="editMovieCategoryInfo">确 定</el-button>
      </span>
    </el-dialog>

  </div>

</template>

<script>
export default {
  name: "MovieCategory",
  // this.$message和this.$confirm都属于原型挂载, 在element.js中配置
  // Vue.prototype.$message = Message
  // Vue.prototype.$confirm = MessageBox.confirm
  data() {
    return {
      queryInfo: {
        query: '',
        pageNum: 1,
        pageSize: 7
      },
      movieCategoryList: [],
      total: 0,
      //控制对话框的显示与隐藏
      addDialogVisible: false,
      //添加类别的表单数据
      addForm: {
        movieCategoryName: ''
      },
      //验证表单规则对象
      addFormRules: {
        movieCategoryName: [
          { required: true, message: '请输入电影类别', trigger: 'blur' }
        ]
      },
      editDialogVisible: false,
      editForm: {},
      editFormRules: {
        movieCategoryName: [
          { required: true, message: '请输入电影类别', trigger: 'blur' }
        ]
      },
      multipleSelection: []
    }
  },
  created() {
    this.getMovieCategoryList()
  },
  methods: {
    getMovieCategoryList() {
      const _this = this;
      axios.get('sysMovieCategory/find', {params: _this.queryInfo}).then(resp => {
        console.log(resp)
        _this.movieCategoryList = resp.data.data;
        _this.total = resp.data.total;
        _this.queryInfo.pageSize = resp.data.pageSize;
        _this.queryInfo.pageNum = resp.data.pageNum;
      })
    },
    handleSizeChange(newSize) {
      this.queryInfo.pageSize = newSize
      this.getMovieCategoryList()
      console.log(newSize)
    },
    handleCurrentChange(newPage) {
      this.queryInfo.pageNum = newPage
      this.getMovieCategoryList()
      console.log(newPage)
    },
    // 监听添加对话框的关闭事件
    addDialogClosed(){
      this.$refs.addFormRef.resetFields()
    },
    // 监听添加按钮
    addMovieCategory(){
      const _this = this;
      this.$refs.addFormRef.validate(async valid => {
        console.log(valid)
        if (!valid) return
        //预校验成功,发网络请求
        axios.defaults.headers.post['Content-Type'] = 'application/json'
        await axios.post('sysMovieCategory', JSON.stringify(_this.addForm)).then(resp => {
          console.log(resp)
          if (resp.data.code !== 200){
            this.$message.error('添加电影类别失败!')
          }
        })
        //隐藏添加对话框
        this.addDialogVisible = false
        //重新加载列表
        this.getMovieCategoryList()
        this.$message.success('添加电影类别成功!')
      })
    },
    // 显示修改对话框,回显数据
    showEditDialog(id){
      const _this = this
      axios.get('sysMovieCategory/' + id ).then(resp => {
        console.log(resp)
        _this.editForm = resp.data.data
      })
      this.editDialogVisible = true
    },
    // 监听修改对话框的关闭事件
    editDialogClosed(){
      this.$refs.editFormRef.resetFields()
    },
    // 修改类别分类信息并提交
    editMovieCategoryInfo(){
      this.$refs.editFormRef.validate(async valid => {
        const _this = this
        if (!valid) return
        axios.defaults.headers.put['Content-Type'] = 'application/json'
        await axios.put('sysMovieCategory', JSON.stringify(_this.editForm)).then(resp => {
          if (resp.data.code !== 200){
            this.$message.error('修改电影类别失败!')
          }
        })
        this.editDialogVisible = false
        this.getMovieCategoryList()
        this.$message.success('修改电影类别成功!')
      })
    },
    // 监听多选框变化
    handleSelectionChange(val){
      this.multipleSelection = val
    },
    async multipleDelete(){
      const _this = this
      //询问用户是否确认删除
      const resp = await this.$confirm('此操作将永久删除这些条目, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).catch(err => err)

      // 用户确认删除, resp为字符串"confirm"
      // 用户取消删除,resp为字符串"cancel"
      if (resp === 'cancel'){
        return _this.$message.info('已取消删除')
      }

      let ids = []
      this.multipleSelection.forEach(data => {
        ids.push(data.movieCategoryId)
      })
      await axios.delete('sysMovieCategory/' + ids).then(resp => {
        if (resp.data.code !== 200){
          this.$message.success('批量删除电影类别失败!')
        }
      })
      this.getMovieCategoryList()
      this.$message.success('批量删除电影类别成功!')
    },
    //根据id删除对应的类别分类
    async deleteMovieCategoryById(id){
      const _this = this
      //询问用户是否确认删除
      const resp = await this.$confirm('此操作将永久删除该条目, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).catch(err => err)

      // 用户确认删除, resp为字符串"confirm"
      // 用户取消删除,resp为字符串"cancel"
      console.log(resp)
      if (resp === 'cancel'){
        return _this.$message.info('已取消删除')
      }

      await axios.delete('sysMovieCategory/' + id).then(resp => {
        if (resp.data.code !== 200){
          _this.$message.success('删除电影类别失败!')
        }
      })
      this.getMovieCategoryList()
      this.$message.success('删除电影类别成功!')
    }
  }
}
</script>

<style scoped>

</style>

用户信息管理

运行截图

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第20张图片

实现

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第21张图片

熊猫系统界面(panda-user)

熊猫管理系统后端

基本框架(panda-framework)

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第22张图片

跨域

package com.panda.framework.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class CrosConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8080")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
}

配置异常处理器,管理全局异常

package com.panda.framework.exception;

import com.panda.common.constant.HttpStatus;
import com.panda.common.exception.DataNotFoundException;
import com.panda.common.response.ResponseResult;
import org.apache.shiro.authc.AuthenticationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.sql.SQLIntegrityConstraintViolationException;

/**
 * 配置异常处理器,管理全局异常
 */
@RestController
@ControllerAdvice
public class GlobalExceptionHandler {

    // Logger控制台显示错误信息
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理验证不通过异常,将错误信息响应给前端
     *
     * @return 错误响应信息
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        //打印日志
        log.error(e.getMessage(), e);
        //获取该异常的结果
        BindingResult bindingResult = e.getBindingResult();
        //获取错误msg
        String msg = bindingResult.getAllErrors().get(0).getDefaultMessage();
        return ResponseResult.error(msg);
    }

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public ResponseResult sqlIntegrityConstraintViolationExceptionHandler(SQLIntegrityConstraintViolationException e) {
        log.error(e.getMessage(), e);
        return ResponseResult.error("插入或修改操作不合法");
    }

    @ExceptionHandler(DataNotFoundException.class)
    public ResponseResult dataNotFoundExceptionHandler(DataNotFoundException e) {
        log.warn(e.getMessage());
        return ResponseResult.error(e.getMessage());
    }

    @ExceptionHandler(NoSuchMethodException.class)
    public ResponseResult noSuchMethodExceptionHandler(NoSuchMethodException e) {
        log.warn(e.getMessage());
        return ResponseResult.error("抱歉,服务器内部出现了些问题");
    }

    @ExceptionHandler(IllegalAccessException.class)
    public ResponseResult illegalAccessExceptionHandler(IllegalAccessException e) {
        log.warn(e.getMessage());
        return ResponseResult.error("抱歉,服务器内部出现了些问题");
    }

    @ExceptionHandler(IOException.class)
    public ResponseResult IOExceptionHandler(IOException e) {
        log.warn(e.getMessage());
        return ResponseResult.error("文件信息错误,原因:" + e.getMessage());
    }

    @ExceptionHandler(AuthenticationException.class)
    public ResponseResult authenticationExceptionHandler(AuthenticationException e) {
        log.warn(e.getMessage());
        return ResponseResult.error(HttpStatus.BAD_REQUEST, e.getMessage());
    }

}

JWT和shiro

如果对jwt不了解请先看:
JWT网络令牌原理和springboot实现

		
		
			org.apache.shiro
			shiro-spring-boot-starter
		

		
			com.auth0
			java-jwt
		

配置shiro安全框架
熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第23张图片

package com.panda.framework.config;

import com.panda.framework.shiro.JwtFilter;
import com.panda.framework.shiro.realms.CustomerRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* 配置shiro安全框架
*/
@Configuration
public class ShiroConfig {

   // 创建shiroFilter
   @Bean("shiroFilter")
   public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
       ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

       // 给shiroFilter设置安全管理器
       shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

       // 配置受限资源
       Map<String, String> map = new LinkedHashMap<>();
       // 放行注册和登录
       map.put("/sysUser/register", "anon");
       map.put("/sysUser/login", "anon");
       // 放行图片查询
       map.put("/images/**", "anon");
       // 放行影院查询请求
       map.put("/sysCinema/**", "anon");
       // 放行电影查找相关请求
       map.put("/sysMovie/find/**", "anon");
       // 放行电影类别查找相关请求
       map.put("/sysMovieCategory/find/**", "anon");
       // 放行电影场次查找相关请求
       map.put("/sysSession/find/**", "anon");

       // 请求这个资源需要认证与授权
       map.put("/sysCinema/update", "jwt");
       // 请求这个资源需要认证与授权
       map.put("/**", "jwt");


       // 添加自己的过滤器并且取名为jwt
       Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
       filterMap.put("jwt", new JwtFilter());
       shiroFilterFactoryBean.setFilters(filterMap);

       //设置认证规则
       shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
       return shiroFilterFactoryBean;
   }

   //创建安全管理器
   @Bean
   public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
       DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

       //给安全管理器设置realm
       defaultWebSecurityManager.setRealm(realm);

       //关闭shiro自带的session,使得不保存登录状态,每次使用token进行验证
       DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
       DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
       defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
       subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
       defaultWebSecurityManager.setSubjectDAO(subjectDAO);

       return defaultWebSecurityManager;
   }

   //创建自定义realm
   @Bean(name = "realm")
   public Realm getRealm() {
       CustomerRealm realm = new CustomerRealm();
       return realm;
   }

}

自定义realm:

package com.panda.framework.shiro.realms;

import com.panda.common.utils.JwtUtil;
import com.panda.framework.shiro.JwtToken;
import com.panda.system.domin.SysUser;
import com.panda.system.service.impl.SysUserServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 自定义realm
 */
@Slf4j
public class CustomerRealm extends AuthorizingRealm {

    @Autowired
    private SysUserServiceImpl sysUserService;

    /**
     * 重写此方法避免shiro报错
     *
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //身份认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getCredentials();
        // 解密获得username,用于和数据库进行对比
        String username = null;
        try {
            //这里工具类没有处理空指针等异常这里处理一下(这里处理科学一些)
            username = JwtUtil.getUsername(token);
        } catch (Exception e) {
            throw new AuthenticationException("token拼写错误或者值为空");
        }
        if (username == null) {
            log.error("token无效(空''或者null都不行!)");
            throw new AuthenticationException("token无效");
        }
        SysUser user = sysUserService.findUserByName(username);
        if (user == null) {
            log.error("用户不存在)");
            throw new AuthenticationException("用户不存在");
        }
        if (!JwtUtil.verify(token, username, user.getPassword())) {
            log.error("用户名或密码错误(token无效或者与登录者不匹配)");
            throw new AuthenticationException("用户名或密码错误(token无效或者与登录者不匹配)");
        }
        return new SimpleAuthenticationInfo(token, token, this.getName());
    }

}

使用jwt过滤器作为shiro的过滤器:

package com.panda.framework.shiro;

import com.panda.common.constant.HttpStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 使用jwt过滤器作为shiro的过滤器
 */
@Slf4j
@Component
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//        把token从header中取出来
        String token = httpServletRequest.getHeader("Token");
        JwtToken jwtToken = new JwtToken(token);

        //交给realm进行登入
        getSubject(request, response).login(jwtToken);
        return true;
    }

    /**
     * 登录认证
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            return executeLogin(request, response);
        } catch (Exception e) {
            log.error("JwtFilter过滤验证失败");
            return false;
        }
    }

    /**
     * 处理跨域
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求, 直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.SUCCESS);
            return false;
        }
        return super.preHandle(request, response);
    }

}

系统(panda-admin)(Controller)

BaseController

抽取重复功能为基类

package com.panda.web.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.panda.common.page.Page;
import com.panda.common.page.PageBuilder;
import com.panda.common.response.ResponseResult;

import java.util.List;

import static com.panda.common.page.PageBuilder.*;

/**
 * 抽取重复功能为基类
 */
public class BaseController {

    /**
     * 开启分页
     */
    public void startPage() {
        Page page = PageBuilder.buildPage();
        Integer pageNum = page.getPageNum();
        Integer pageSize = page.getPageSize();
        if (pageNum != null && pageSize != null) {
            PageHelper.startPage(pageNum, pageSize, page.getOrderByColumn());
        }
    }

    /**
     * 根据修改行数返回响应消息
     * @param rows
     * @return
     */
    public ResponseResult getResult(int rows) {
        return rows == 0 ? ResponseResult.error() : ResponseResult.success();
    }

    /**
     * 分页响应消息
     * @param data
     * @return
     */
    public ResponseResult getResult(List<?> data) {
        PageInfo pageInfo = new PageInfo(data);
        ResponseResult responseResult = ResponseResult.success(data);
        responseResult.put(PAGE_NUM, pageInfo.getPageNum());
        responseResult.put(PAGE_SIZE, pageInfo.getPageSize());
        responseResult.put(TOTAL, pageInfo.getTotal());
        return responseResult;
    }

    /**
     * 对象类型响应消息
     * @param data
     * @return
     */
    public ResponseResult getResult(Object data) {
        return ResponseResult.success(data);
    }

}

SysUserController

package com.panda.web.controller.system;

import com.panda.common.response.ResponseResult;
import com.panda.system.domin.SysUser;
import com.panda.system.domin.vo.SysUserVo;
import com.panda.system.service.impl.SysUserServiceImpl;
import com.panda.web.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class SysUserController extends BaseController {
    @Autowired
    private SysUserServiceImpl sysUserService;

    @GetMapping("/sysUser")
    public ResponseResult findAllUsers(SysUser sysUser) {
        startPage();
        List<SysUser> data = sysUserService.findAllUsers(sysUser);
        return getResult(data);
    }

//    根据id查询
    @GetMapping("/sysUser/{id}")
    public ResponseResult findUserById(@PathVariable Long id) {
        return getResult(sysUserService.findUserById(id));
    }

    /**
     * 添加用户请求,注册也在这里
     * @param sysUser
     * @return
     */
    @PostMapping("/sysUser")
    public ResponseResult addUser(@Validated @RequestBody SysUser sysUser) {
        return getResult(sysUserService.addUser(sysUser));
    }

    /**
     *更新
     * @param sysUser
     * @return
     */
    @PutMapping("/sysUser")
    public ResponseResult updateUser(@Validated @RequestBody SysUser sysUser) {
        return getResult(sysUserService.updateUser(sysUser));
    }

    /**
     *删除
     * @param ids
     * @return
     */
    @DeleteMapping("/sysUser/{ids}")
    public ResponseResult deleteUser(@PathVariable Long[] ids) {
        return getResult(sysUserService.deleteUser(ids));
    }

    /**
     * 用户登录请求
     *
     * @param sysUserVo 封装用户登录输入的信息
     * @return
     */
    @RequestMapping("/sysUser/login")
    public ResponseResult login(@RequestBody SysUserVo sysUserVo) {
        return getResult(sysUserService.login(sysUserVo));
    }

    /**
     * 用户注册请求
     *
     * @param sysUser
     * @return
     */
    @PostMapping("/sysUser/register")
    public ResponseResult register(@Validated @RequestBody SysUser sysUser) {
        // 注册只接收部分参数值,重新建立一个实例对象只接受注册接受的参数
        SysUser registerUserInfo = new SysUser();
        registerUserInfo.setUserName(sysUser.getUserName());
        registerUserInfo.setPassword(sysUser.getPassword());
        registerUserInfo.setSex(sysUser.getSex());
        registerUserInfo.setPhoneNumber(sysUser.getPhoneNumber());
        return getResult(sysUserService.addUser(registerUserInfo));
    }

}

SysCinemaController

这个影院的基本信息

package com.panda.web.controller.system;

import com.panda.common.response.ResponseResult;
import com.panda.system.domin.SysCinema;
import com.panda.system.service.impl.SysCinemaServiceImpl;
import com.panda.web.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;


@RestController
public class SysCinemaController extends BaseController {

    @Autowired
    private SysCinemaServiceImpl sysCinemaService;


    @GetMapping("/sysCinema")
    public ResponseResult findCinema() {
        return getResult(sysCinemaService.findCinema());
    }

    @PutMapping("/sysCinema/update")
    public ResponseResult updateCinema(@Validated @RequestBody SysCinema sysCinema) {
        return getResult(sysCinemaService.updateCinema(sysCinema));
    }

    @GetMapping(value = {"/sysCinema/find/{cinemaId}/{movieId}", "/sysCinema/find/{cinemaId}"})
    public ResponseResult findCinemaById(@PathVariable Long cinemaId, @PathVariable(required = false) Long movieId) {
        SysCinema cinema = sysCinemaService.findCinemaById(cinemaId);
        if (movieId == null || movieId == 0) {
            movieId = cinema.getSysMovieList().size() > 0 ? cinema.getSysMovieList().get(0).getMovieId() : 0;
        }


        HashMap<String, Object> response = new HashMap<>();
//        将查到的信息放在响应体中
        response.put("cinema", cinema);
        return getResult(response);
    }

}

熊猫电影网站(springboot+ssm+vue+element+shiro+jwt+redis)前后端分离_第24张图片

SysRoleController

package com.panda.web.controller.system;

import com.panda.common.response.ResponseResult;
import com.panda.system.domin.SysRole;
import com.panda.system.service.impl.SysRoleServiceImpl;
import com.panda.web.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class SysRoleController extends BaseController {

    @Autowired
    SysRoleServiceImpl sysRoleService;

    @GetMapping("/sysRole")
    public ResponseResult findAllRoles() {
        startPage();
        List<SysRole> data = sysRoleService.findAllRoles();
        return getResult(data);
    }

    @GetMapping("/sysRole/{id}")
    public ResponseResult findRoleById(@PathVariable Long id) {
        return getResult(sysRoleService.findRoleById(id));
    }

    @PostMapping("/sysRole")
    public ResponseResult addRole(@Validated @RequestBody SysRole sysRole) {
        return getResult(sysRoleService.addRole(sysRole));
    }

    @PutMapping("/sysRole")
//    @Validated 来检验数据,如果数据异常则抛出异常,统一处理。
    public ResponseResult updateRole(@Validated @RequestBody SysRole sysRole) {
        return getResult(sysRoleService.updateRole(sysRole));
    }

    @DeleteMapping("/sysRole/{ids}")
    public ResponseResult deleteRole(@PathVariable Long[] ids) {
        return getResult(sysRoleService.deleteRole(ids));
    }

    /**
     * 给指定 id 的角色分配权限,包括增加或者删除权限
     * @param roleId
     * @param keys
     * @return
     */
    @PostMapping("/sysRole/{roleId}")
    public ResponseResult allotRight(@PathVariable Long roleId, @RequestBody Long[] keys) {
        return getResult(sysRoleService.allotRight(roleId, keys));
    }

}

SysResourceController

系统(panda-system)(service,dao,xml)

基本思路:
controller——>service——>dao——>xml

用户注册

    /**
     * 用户注册请求
     *
     * @param sysUser
     * @return
     */
    @PostMapping("/sysUser/register")
    public ResponseResult register(@Validated @RequestBody SysUser sysUser) {
        // 注册只接收部分参数值,重新建立一个实例对象只接受注册接受的参数
        SysUser registerUserInfo = new SysUser();
        registerUserInfo.setUserName(sysUser.getUserName());
        registerUserInfo.setPassword(sysUser.getPassword());
        registerUserInfo.setSex(sysUser.getSex());
        registerUserInfo.setPhoneNumber(sysUser.getPhoneNumber());
        return getResult(sysUserService.addUser(registerUserInfo));
    }

//处理密码:md5 + salt + hash散列

 /**
     * 处理注册逻辑
     *
     * @param sysUser
     * @return
     */
    @Override
    public int addUser(SysUser sysUser) {
        if (!isUserNameUnique(sysUser.getUserName(), -1L)) {
            throw new AuthenticationException("用户名重复");
        }
        //处理密码:md5 + salt + hash散列
        String salt = SaltUtils.getSalt(8);
        Md5Hash md5Hash = new Md5Hash(sysUser.getPassword(), salt, 1024);

        sysUser.setPassword(md5Hash.toHex());
        sysUser.setSalt(salt);
        return sysUserMapper.addUser(sysUser);
    }
   <update id="addUser" parameterType="SysUser">
        
        <selectKey keyProperty="userId" keyColumn="user_id" resultType="long" order="AFTER">
            select last_insert_id();
        selectKey>
        insert into sys_user(
        user_name,
        password,
        salt,
        <if test="email != null and email != ''">email,if>
        <if test="phoneNumber != null and phoneNumber != ''">phone_number,if>
        <if test="userPicture != null and userPicture != ''">user_picture,if>
        <if test="roleId != null and roleId != 0"> role_id, if>
        sex
        )
        values(
        #{userName},
        #{password},
        #{salt},
        <if test="email != null and email != ''">#{email},if>
        <if test="phoneNumber != null and phoneNumber != ''">#{phoneNumber},if>
        <if test="userPicture != null and userPicture != ''">#{userPicture},if>
        <if test="roleId != null and roleId != 0">#{roleId}, if>
        #{sex})
    update>

用户登录

    /**
     * 用户登录请求
     *
     * @param sysUserVo 封装用户登录输入的信息
     * @return
     */
    @RequestMapping("/sysUser/login")
    public ResponseResult login(@RequestBody SysUserVo sysUserVo) {
        return getResult(sysUserService.login(sysUserVo));
    }

 @Override
    public LoginUser login(SysUserVo sysUserVo) {
        //登录,先查询用户信息
        SysUser user = sysUserMapper.findUserByName(sysUserVo.getUserName());
        if (user == null) {
            throw new AuthenticationException("用户名不存在");
        }

        //验证密码(拿到的password+salt)
        Md5Hash md5Hash = new Md5Hash(sysUserVo.getPassword(), user.getSalt(), 1024);
        if (!user.getPassword().equals(md5Hash.toHex())) {
            throw new AuthenticationException("用户名或密码错误");
        }

        //设置登录用户对象
        LoginUser loginUser = findLoginUser(sysUserVo);

        //颁发token
        String token = JwtUtil.sign(user.getUserName(), user.getPassword());

        loginUser.setToken(token);
        return loginUser;
    }
   
     @Override
    public LoginUser findLoginUser(SysUserVo sysUserVo) {
        return sysUserMapper.findLoginUser(sysUserVo);
    }
   <select id="findUserByName" resultMap="SysUserMap" parameterType="String">
        select * from sys_user where user_name = #{userName} and del_state = 0
    </select>


    <!-- 查询登录用户信息 -->
    <select id="findLoginUser" parameterType="SysUserVo" resultMap="loginUserMap">
        select * from sys_user where user_name = #{userName} limit 0,1
    </select>

角色管理(权限)

package com.panda.system.domin;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.List;


@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class SysRole implements Serializable {

    private static final Long serialVersionUID = 1L;

    private Long roleId;

    //角色名称
    @NotBlank(message = "角色名称不能为空")
    private String roleName;

    //角色描述
    @NotBlank(message = "角色描述不能为空")
    private String roleDesc;


    //角色拥有的权限,分多级权限存储,取名为children方便读取所有权限
    private List<SysResource> children;
}


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.panda.system.mapper.SysRoleMapper">

    <resultMap id="SysRoleMap" type="SysRole">
        <id property="roleId" column="role_id">id>
        <result property="roleName" column="role_name">result>
        <result property="roleDesc" column="role_desc">result>
        <collection property="children" column="role_id" ofType="SysResource" select="com.panda.system.mapper.SysRoleMapper.findByRoleId">
        collection>
    resultMap>

    <resultMap id="OneRoleMap" type="SysResource">
        <id property="id" column="resource_id">id>
        <result property="name" column="resource_name">result>
        <result property="path" column="path">result>
        <result property="level" column="level">result>
        <result property="parentId" column="parent_id">result>    
        <collection property="children" column="resource_id" ofType="SysResource">
            <id property="id" column="sid">id>
            <result property="name" column="sname">result>
            <result property="path" column="spath">result>
            <result property="level" column="slevel">result>
            <result property="parentId" column="sparent_id">result>    
            <collection property="children" column="resource_id" ofType="SysResource">
                <id property="id" column="gsid">id>
                <result property="name" column="gsname">result>
                <result property="path" column="gspath">result>
                <result property="level" column="gslevel">result>
                <result property="parentId" column="gsparent_id">result>    
            collection>
        collection>
    resultMap>

    <select id="findAllRoles" resultMap="SysRoleMap">
        select * from sys_role sysr
    select>


    <select id="findByRoleId" resultMap="OneRoleMap" parameterType="Long">
        select resources.* from sys_role sysr
        left join sys_role_resource srr on sysr.role_id = srr.role_id
        left join
            (select sr.*, sr1.resource_id sid, sr1.resource_name sname, sr1.path spath, sr1.level slevel, sr1.parent_id sparent_id,
                    sr2.resource_id gsid, sr2.resource_name gsname, sr2.path gspath, sr2.level gslevel, sr2.parent_id gsparent_id from sys_resource sr
                    left join sys_resource sr1 on sr1.parent_id = sr.resource_id
                    left join sys_resource sr2 on sr2.parent_id = sr1.resource_id
                    where sr.level = 1) resources on srr.resource_id = resources.gsid where sysr.role_id = #{id}
    select>

    <select id="findRoleById" resultType="SysRole" parameterType="long">
        select * from sys_role where role_id = #{id}
    select>

    <update id="addRole" parameterType="SysRole">
        insert into sys_role(role_name, role_desc) values(#{roleName}, #{roleDesc})
    update>

    <update id="updateRole" parameterType="SysRole">
        update sys_role set role_name = #{roleName}, role_desc = #{roleDesc} where role_id = #{roleId}
    update>

    <update id="deleteRole" parameterType="Long">
        delete from sys_role where role_id = #{id}
    update>


    
    <update id="addRight">
        insert into sys_role_resource values(#{roleId}, #{resourceId})
    update>

    
    <update id="deleteRight">
        delete from sys_role_resource where role_id = #{roleId} and resource_id = #{resourceId}
    update>

    
    <select id="findAllRights" resultType="Long" parameterType="Long">
        select resource_id id from sys_role_resource where role_id = #{roleId}
    select>

mapper>

你可能感兴趣的:(SpringBoot,vue.js,spring,boot,echarts)