管理系统实战-前后端分离
第一章 项目需求分析和技术架构
1.1 项目需求
订单管理系统采用数据化管理订单、管理商品进销、供应商信息维护、员工管理等加快对店铺运营效率。 项目涉及功能模块:订单管理、供应商管理、商品管理、员工管理。
1.2 什么是前后端分离开发
传统系统架构:
-
前端工程师负责编写HTML页面,完成前端页面设计。
-
后端工程师使用模板技术将HTML页面代码转换为 JSP 页面,同时内嵌后端代码 (如Java);
前后端强依赖,后端必须要等前端的HTML开发好才能套转换成JSP。如果需求变更,前端HTML要改,后端
JSP也要跟着变, 这是件紧紧牵绊的事,使得开发效率降低。
-
产品交付时,要将前后端代码全部进行打包,部署到同一服务器上,或者进行简单的动静态分离部署。
前后端分离架构:
-
前后端约定好API接口&数据&参数
-
前后端并行开发
前端工程师只需要编写HTML页面,通过HTTP请求调用后端提供的接口服务即可。后端只要愉快的开发接口就行了。
无强依赖,如果需求变更,只要接口和参数不变,就不用两边都修改代码,开发效率高。
-
除了开发阶段分离、在运行期前后端资源也会进行分离部署。
前后端分离已成为互联网项目开发的业界标准使用方式。传统的前后端混合开发模式,虽然久经考验,到现在依然 还是能够支撑起应用的开发。但是放眼未来,社会分工更加精细化,前后端分离开发的精细化也是必然趋势。并且 前后端分离会为以后的大型分布式架构、微服务架构、多端化服务(多种客户端,例如:浏览器,安卓,IOS, 车载终端等等)打下坚实的基础。
1.3 项目前端技术架构
第二章 项目环境搭建
2.1 基于 Vue-CLI 3.x 创建项目
2.1.1 Vue CLI 安装
-
全局安装 Vue-CLI
npm install -g vue npm install -g @vue/[email protected]
-
安装成功后,在命令行可以使用 vue 命令, 比如 查看版本
# 大写V vue -V
2.1.2 Vue CLI 创建项目
创建项目命令:vue create 项目名称 会员管理系统 (Member Management System, 简称 MMS )
vue create ssm
2.1.3 启动项目测试
启动项目:
npm run serve
2.2 初始化项目
2.2.1 更改标题
找到 public\index.html 页面,修改 title 内容: 超市订单管理系统
2.2.2 配置 vue.config.js
在 ssm 根目录下创建 vue.config.js ,添加如下配置:
module.exports = { devServer: { port: 8888, // 端口号,如果端口号被占用,会自动提升1 host: "localhost", //主机名, 127.0.0.1, 真机 0.0.0.0 https: false, //协议 open: true, //启动服务时自动打开浏览器访问 proxy: { // 开发环境代理配置 // '/dev-api': { [process.env.VUE_APP_BASE_API]: { // 目标服务器地址,代理访问 http://localhost:8001 target: process.env.VUE_APP_SERVICE_URL, changeOrigin: true, // 开启代理服务器, pathRewrite: { // /dev-api/db.json 最终会发送 http://localhost:8001/db.json // 将 请求地址前缀 /dev-api 替换为 空的, // '^/dev-api': '', ['^' + process.env.VUE_APP_BASE_API]: '' } } } }, lintOnSave: false, // 关闭格式检查 productionSourceMap: false, // 打包时不会生成 .map 文件,加快打包速度 }
添加配置环境
.env.development内容如下
# 只有以 VUE_APP_ 开头的变量会被 webpack 静态嵌入到项目中进行使用 process.env.VUE_APP_xxxxxx # 目标服务接口地址,这个地址是按照你自已环境来的, 添加 或者更改配置后,需要重启服务 VUE_APP_SERVICE_URL = 'http://localhost:8081' # 开发环境的前缀 VUE_APP_BASE_API = '/dev-api' .env.production内容差不多,将dev改成pro就考研了
2.2.3 整合第三方库
-
安装 axios , 处理异步请求
npm i -S axios
2.3 整合 ElementUI
2.3.1 ElementUI 简介
Element 是饿了么平台推出的一套基于 Vue.js 开发的后台页面组件库。 官网:Element - The world's most popular Vue UI framework
2.3.2 ElementUI 安装
将 element-ui 模块通过本地安装为生产依赖。在 ssm 目录下的命令行窗口,输入以下命令:
npm i -S element-ui
2.3.3 完整引入 ElementUI
在 ssm\src\main.js 中导入 element-ui 和 element-ui/lib/theme-chalk/index.css , 使用 Vue.use(ElementUI)
import Vue from "vue"; import ElementUI from 'element-ui'; // 组件库 import 'element-ui/lib/theme-chalk/index.css'; // 样式 import App from "./App.vue"; // 使用 ElementUI Vue.use(ElementUI); Vue.config.productionTip = false new Vue({ router, render: h => h(App), }).$mount('#app')
第三章 项目布局
3.1 在component添加三个文件夹分别是:AppHeader,AppMain,AppNavbar和一个Layout.vue
3.1.1 AppHeader内容是
<template> <div class="header"> <a href="#/"> <img class="logo" src="@/assets/logo.png" width="25px"> <span class="company">超市订单管理系统</span> </a> <el-dropdown> <div class="avatar-wrapper"> <img src="https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" class="user-avatar"> <i class="el-icon-caret-bottom"/> </div> <el-dropdown-menu slot="dropdown"> <el-dropdown-item icon="el-icon-edit" command="a">修改密码</el-dropdown-item> <el-dropdown-item icon="el-icon-s-fold" command="b">退出系统</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </template> <script> export default { } </script> <style scoped> .user-avatar { vertical-align:middle; width: 40px; height:40px; border-radius: 10px; } .logo{ vertical-align: middle; padding: 0px 10px 0 40px; } .company { position: absolute; color: white; } /* 下拉菜单 */ .el-dropdown { float: right; margin-right: 40px; } .el-dropdown-link { color: white; cursor: pointer; } </style>
3.1.2 AppMain内容是
<template> <div class="main"> 这里是展示数据地方 <!-- <router-view></router-view> --> </div> </template> <script> </script>
3.1.3 AppNavbar内容是
<template> <div class="navbar"> <!-- default-active : 默认选中的菜单 :router="true" true表示开启路由模式,开启之后, index值代表的就是路由地址 --> <el-menu :router="true" :default-active="$route.path" class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b"> <el-menu-item index="/home"> <i class="el-icon-s-home"></i> <span slot="title">首页</span> </el-menu-item> <el-menu-item index="/bill/"> <i class="el-icon-user-solid"></i> <span slot="title">订单管理</span> </el-menu-item> <el-menu-item index="/provider/"> <i class="el-icon-s-cooperation"></i> <span slot="title">供应商管理</span> </el-menu-item> <el-menu-item index="/user/"> <i class="el-icon-s-goods"></i> <span slot="title">用户管理</span> </el-menu-item> <el-menu-item index="/user-update/"> <i class="el-icon-user"></i> <span slot="title">密码修改</span> </el-menu-item> </el-menu> </div> </template> <style scoped> .el-menu { border-right: none; } </style>
3.2 布局layout.vue的内容
<template> <div> <app-header></app-header> <app-navbar></app-navbar> <app-main></app-main> </div> </template> <script> // 会导入 ./AppHeader 下面的 index.vue组件 import AppHeader from './AppHeader' import AppNavbar from './AppNavbar' import AppMain from './AppMain' export default { components: {AppHeader, AppNavbar, AppMain} } </script> <style scoped> /* 头部区域 */ .header { position: absolute; line-height: 50px; top: 0px; left: 0px; right: 0px; background-color: #2d3a4b } .navbar { position: absolute; width: 230px; top: 50px; left: 0px; bottom: 0px; overflow-y: auto; background-color: #545c64; } .main { position: absolute; top: 50px; left: 230px; right: 0px; bottom: 0px; padding: 10px; overflow-y: auto; /* background-color: red; */ } </style>
3.3 添加路由 在src下创建router.js
安装 vue-router
import Vue from "vue"; import Router from "vue-router"; import Layout from '@/components/Layout.vue' Vue.use(Router); export default new Router({ routes: [{ path: '/', name: 'layout', //路由名称 component: Layout, //组件对象 }, ] })
3.4加入管理
3.5启动测试
第四章 Axios 封装和跨域问题
4.1 封装 Axios 对象
因为项目中很多组件中要通过 Axios 发送异步请求,所以封装一个 Axios 对象。自已封装的 Axios 在后续可以使用
安装
npm install axios
axios 中提供的拦截器。
-
在 src 目录下创建 utils 目录及 utils 下面创建 request.js 文件
内容如下
import axios from 'axios' import { Loading, Message } from 'element-ui'; const loading = { loadingInstance: null, // Loading 实例 // 打开加载 open: function() { // 创建实例,而且会打开加载 窗口 if (this.loadingInstance === null) { this.loadingInstance = Loading.service({ target: '.main', text: '系统加载中...', background: 'rgba(0, 0, 0, 0.5)' }) } }, // 关闭加载 close: function() { // 不为空时, 则关闭加载窗口 if (this.loadingInstance !== null) { this.loadingInstance.close() } this.loadingInstance = null } } const request = axios.create({ // baseURL: '/dev-api', baseURL: process.env.VUE_APP_BASE_API, // baseURL: '/', timeout: 5000 // 请求超时,5000毫秒 }) // 请求拦截器 request.interceptors.request.use(config => { // 打开加载窗口 loading.open() return config }, error => { // 关闭加载窗口 loading.close() // 出现异常 return Promise.reject(error); }) // 响应拦截器 request.interceptors.response.use(response => { // 关闭加载窗口 loading.close() const resp = response.data // 后台正常响应的状态,如果不是 2000, 说明后台处理有问题 if (resp.code !== 2000) { Message({ message: resp.message || '系统异常', type: 'warning', duration: 5 * 1000 }) } // return response.data // 可以在这里统一的获取后台响应的数据进行返回,而这里面就没有请求头那些 return response }, error => { // 关闭加载窗口 loading.close() Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error); }) export default request // 导出自定义创建 axios 对象
2 在 src 目录下创建 utils 目录及 utils 下面创建 auth.js 文件
const TOKEN_KEY = 'ssm-token' const USER_KEY = 'ssm-user' // 获取 token export function getToken() { return localStorage.getItem(TOKEN_KEY) } // 保存 token export function setToken(token) { return localStorage.setItem(TOKEN_KEY, token) } // 获取用户信息 export function getUser() { return JSON.parse(localStorage.getItem(USER_KEY)) } //保存用户信息 export function setUser(user) { localStorage.setItem(USER_KEY, JSON.stringify(user)) } //移除用户信息 export function removeToken() { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(USER_KEY) }
4.2添加状态管理
4.2.1创建用户信息管理
4.2.2 在main.js添加
4.2.3 安装vues
npm install vuex --save
4.2.4 index.js的内容
import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' Vue.use(Vuex) const store = new Vuex.Store({ modules: { user } }) export default store
4.2.4 user.js信息内容
import {getToken, setToken, setUser, getUser, removeToken} from '@/utils/auth' import { login, getUserInfo, logout} from '@/api/login' const user = { state: { token: getToken(), // getToken() 作为token初始值,解决刷新页面之后token为null user: getUser() }, mutations: { SET_TOKEN(state, token) { state.token = token setToken(token) }, SET_USER(state, user) { state.user = user setUser(user) } }, actions: { // 登录获取token Login({commit}, form) { // resolve 触发成功处理, reject 触发异常处理 return new Promise((resolve, reject) => { login(form.username.trim(), form.password).then(response => { const resp = response.data // 获取到的就是响应的data数据 commit('SET_TOKEN', resp.data.token) // 通过组件已经将token更新成功 resolve(resp) }).catch(error => { reject(error) }) }) }, // 通过token获取用户信息 GetUserInfo({commit, state}) { return new Promise((resolve, reject) => { getUserInfo(state.token).then(response => { const respUser = response.data commit('SET_USER', respUser.data) resolve(respUser) }).catch(error => { reject(error) }) }) }, // 退出 Logout({commit, state}) { return new Promise((resolve, reject) => { logout(state.token).then(response => { const resp = response.data commit('SET_TOKEN', '') commit('SET_USER', null) removeToken() resolve(resp) }).catch(error => { reject(error) }) }) } } } export default user
4.3添加登录功能测试
4.3.1 login.js内容
import request from '@/utils/request' export function login(username, password) { return request({ // Promise url: '/user/login', method: 'post', data: { username, // username: username password } }) } export function getUserInfo(token) { return request({ url: `/user/info/${token}`, method: 'get' }) } export function logout(token) { return request({ url: `/user/logout`, method: 'post', data: { token //token: token } }) }
4.3.2 在compoments 下面的AppMain下面的inde.vue测试
内容如下:
<template> <div class="main"> <p><button @click="log">add</button></p> <router-view></router-view> </div> </template> <script> export default { data(){ return { loading, loginForm: { username: 'admin', password: 'admin' }, } } , methods: { // 提交登录 log:function () { //调用登录方法 this.$store.dispatch('GetUserInfo', this.loginForm).then((resp) => { this.loading=true console.log("-------",resp.data) }).catch(() => { this.loading = false }) }, } } </script>
第五章 系统登录和退出管理
5.1 需求分析
开发登录页面,当输入帐号和密码验证通过后,才允许进行到首页。效果图如下
5.2 路由配置
-
在 src\views 目录下新建 login 目录及此目录下新建文件 index.vue 说明:通过 import Login from './views/login' 导入组件,当前只指定了组件路径,默认导入的就是指定路径 下的 index.vue 组件
-
在 src\router.js 中配置路由(把原有的路由配置删除),如下:
import Vue from "vue"; import Router from "vue-router"; import Layout from '@/components/Layout.vue' import Login from '@/views/login' Vue.use(Router); export default new Router({ routes: [ { // 登录页 path: '/login', name: 'login', //路由名称 component: Login }, { path: '/', name: 'layout', //路由名称 component: Layout, //组件对象 }, ] })
5.3 登录页面
<template> <div class="login-container"> <el-form ref="form" :rules="rules" :model="form" label-width="80px" class="login-form"> <h2 class="login-title">超市订单管理系统</h2> <el-form-item label="帐号" prop="username"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input v-model="form.password" type="password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('form')">登录</el-button> </el-form-item> </el-form> </div> </template> <script> import {login, getUserInfo} from '@/api/login' export default { data() { return { form: { username: '', password: '' }, rules: { username: [ {required: true, message: '帐号不能为空', trigger: 'blur' }, ], password: [ {required: true, message: '密码不能为空', trigger: 'blur' }, ] } } }, methods: { // 注意:按钮上调用的函数名要一致 submitForm submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { // 验证帐号和密码是否通过, login(this.form.username, this.form.password).then(response => { const resp = response.data console.log(resp.code, resp.message, resp.data.token, resp.code === 2000) if (resp.message) { // 通过,获取用户信息 异步请求 getUserInfo(resp.data.token).then(response => { // 存入session中 const respUser = response.data if (respUser.message) { // 将信息保存到浏览器的 localStorage 中 localStorage.setItem('ssm-user', JSON.stringify(respUser.data)) // 方便后面重新验证 localStorage.setItem('ssm-token', resp.data.token) // 前往首页 this.$router.push("/") }else { // 获取信息失败, 弹出警告 this.$message({ message: response.message, type: 'waring' }) } }).catch(error => { }) }else{ console.log('验证失败') return false } }) } }) } } } </script> <style scoped> .login-form { width: 350px; /* 上下间隙 160px, 左右自动居中 */ margin: 160px auto; background-color: rgb(255,255,255,0.8); padding: 28px; border-radius: 20px; } .login-container { position: absolute; width: 100%; height: 100%; background: url('../../assets/login.jpg') } .login-title { color: #303133; text-align: center; } </style>
用户校验工具匹对校验
// 校验用户名是否合法 只允许4-30位中文、数字、字母和下划线 export function isvalidUsername(str) { const valid_map = /^[a-zA-Z0-9_\u4e00-\u9fa5]{4,30}$/ return valid_map.test(str) } // 校验手机号是否合法 export function isvalidMobile(str) { const valid_map = 11 && /^1(3|4|5|6|7|8|9)\d{9}$/ return valid_map.test(str) } // 校验邮箱是否合法 export function isvalidEmail(str) { const valid_map = /^[A-Za-z0-9_.-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/ return valid_map.test(str) } /* 合法uri*/ export function validateURL(textval) { const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ return urlregex.test(textval) }
第二个登录页面
<template> <div class="login_page"> <div class="login_box"> <div class="center_box"> <!-- 登录&注册--> <div :class="{login_form: true, rotate: tab == 2}"> <div :class="{tabs: true, r180: reverse == 2}"> <div class="fl tab" @click="changetab(1)"> <span :class="{on: tab == 1}">登录</span> </div> <div class="fl tab" @click="changetab(2)"> <span :class="{on: tab == 2}">注册</span> </div> </div> <!-- 登录 --> <div class="form_body" v-if="reverse == 1"> <!-- submit.prevent 阻止默认表单事件提交,采用loginSubmit --> <form @submit.prevent="loginSubmit"> <input type="text" v-model="loginData.username" placeholder="请输入用户名" autocomplete="off"> <input type="password" v-model="loginData.password" placeholder="请输入密码" autocomplete="off"> <div class="error_msg">{{loginMessage}}</div> <input type="submit" v-if="subState" disabled="disabled" value="登录中···" class="btn" /> <input type="submit" v-else value="登录" @submit="loginSubmit" class="btn" /> </form> </div> <!-- 注册 --> <div class="form_body r180" v-if="reverse == 2"> <form @submit.prevent="regSubmit"> <input type="text" v-model="registerData.username" placeholder="请输入用户名" autocomplete="off"> <input type="password" v-model="registerData.password" placeholder="6-30位密码,可用数字/字母/符号组合" autocomplete="off"> <input type="password" v-model="registerData.repPassword" placeholder="确认密码" > <div class="error_msg">{{regMessage}}</div> <div class="agree"> <input type="checkbox" id="tonyi" v-model="registerData.check"> <label for="tonyi">我已经阅读并同意</label><a href="jvascript:;" @click="xieyi = true">《用户协议》</a> </div> <input type="submit" v-if="subState" disabled="disabled" value="提交中···" class="btn"> <input type="submit" v-else value="注册" class="btn"> </form> </div> </div> </div> </div> <!-- 用户协议 --> <div class="xieyi" v-if="xieyi" @click.self="xieyi = false"> <div class="xieyi_content"> <div class="xieyi_title">请认真阅读用户协议</div> <div class="xieyi_body">123 </div> <input type="button" class="xieyi_btn" value="确定" @click="xieyi = false"> </div> </div> </div> </template> <script > import {isvalidUsername} from '@/utils/validate' import {getXieyi,getUserUsername,register} from '@/api/login' export default { data () { return { tab: 1, // 高亮当前标签名 reverse: 1, // 旋转 1 登录,2 注册 loginMessage: '', //登录错误提示信息 regMessage: '', //注册错误提示信息 subState: false, //提交状态 xieyi: false, // 显示隐藏协议内容 xieyiContent: null, // 协议内容 redirectURL: '//www.zpark.com.cn', // 登录成功后重写向地址 loginData: { // 登录表单数据 username: '', password: '' }, registerData: { // 注册表单数据 username: '', password: '', repPassword: '', check: false }, } }, async created () { if(this.$route.query.redirectURL){ this.redirectURL = this.$route.query.redirectURL } //获取协议 this.xieyiContent= await getXieyi(); }, methods: { // 切换标签 changetab (int) { this.tab = int; let _that = this; setTimeout(() => { this.reverse = int }, 200) }, // 提交登录 loginSubmit() { if(!isvalidUsername(this.loginData.username)){ this.loginMessage= '用户名必须是4-30位中文、数字、字母和下划' return false } if(this.loginData.password.length < 6){ this.loginMessage= '密码必须是大于6位数' return false } //调用登录方法 this.$store.dispatch("UserLogin",this.loginData).then(response=>{ const {code,message} = response if(code == 20000){ window.location.href=this.redirectURL }else{ this.loginMessage= message } this.subState= false //提交完成 }).catch(error=>{ this.subState = false this.loginMessage= '系统繁忙' }) }, // 提交注册 async regSubmit() { //判断是否别注册 const {code,message,data}= await getUserUsername(this.registerData.username); if(code !== 20000){ this.regMessage=message return false } if(data){ this.regMessage='该用户已经被注册,请换一个' return false } //判断用户名 if(!isvalidUsername(this.registerData.username)){ this.regMessage= '用户名必须是4-30位中文、数字、字母和下划' return false } //判断密码 if(this.registerData.password.length < 6 || this.registerData.password.length >30){ this.regMessage= '密码必须是大于6位,并且小于30位' return false } //判断两次密码是否相同 if(this.registerData.password !== this.registerData.repPassword){ this.regMessage= '你输入的两次密码不相同' return false } if(!this.registerData.check){ this.regMessage='请选择协议' return false } this.subState= true //注册中 //调用注册的方法进行注册 register(this.registerData).then(response=>{ this.subState= false const {code,message} =response if(code == 20000){ this.changetab(1) }else{ this.regMessage=message } }).catch(errpr=>{ this.subState =false this.regMessage = '系统繁忙' }) } }, } </script> <style scoped> @import '../../assets/style/login.css'; </style>
5.4 权限&退出
<template> <div class="header"> <a href="#/"> <img class="logo" src="@/assets/logo.png" width="25px"> <span class="company">超市订单管理系统</span> </a> <el-dropdown @command="handleCommand"> <div class="avatar-wrapper"> <img :src="user.avatar+'?imageView2/1/w/70/h/70'" class="user-avatar"> <i class="el-icon-caret-bottom"/> </div> <el-dropdown-menu slot="dropdown"> <el-dropdown-item icon="el-icon-edit" command="a">修改密码</el-dropdown-item> <el-dropdown-item icon="el-icon-s-fold" command="b">退出系统</el-dropdown-item> </el-dropdown-menu> </el-dropdown> <!-- 修改密码 --> <el-dialog title="修改密码" :visible.sync="dialogFormVisible" width="400px"> <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" style="width: 300px"> <el-form-item label="原密码" prop="oldPass"> <el-input type="password" v-model="ruleForm.oldPass" autocomplete="off"></el-input> </el-form-item> <el-form-item label="新密码" prop="pass"> <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input> </el-form-item> <el-form-item label="确认密码" prop="checkPass"> <el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button> <el-button @click="$refs['ruleForm'].resetFields()">重置</el-button> </el-form-item> </el-form> </el-dialog> </div> </template> <script> import {logout} from '@/api/login' // import passwordApi from '@/api/password' export default { data() { // 在return 上面进行申明自定校验 const validateOldPass = (rule, value, callback) => { // console.log(this.user.id) passwordApi.checkPwd(this.user.id, value).then(response => { const resp = response.data if(resp.flag) { // 验证通过 callback() }else { callback(new Error( resp.message )) } }) } // 校验确认密码是否一致 const validatePass = (rule, value, callback) => { // value 代表 checkPass if(value !== this.ruleForm.pass) { callback(new Error('两次输入的密码不一致')) }else { // 相等,则通过 callback() } } // 注意:在 return 上面,而上面不能使用 逗号 , 结束 return { user: this.$store.state.user.user, dialogFormVisible: false, ruleForm: { oldPass: '', pass: '', checkPass: '' }, rules: { oldPass: [ {required: true, message: '原密码不能为空', trigger: "blur"}, { validator: validateOldPass, trigger: 'blur' } ], pass: [ {required: true, message: '新密码不能为空', trigger: "blur"} ], checkPass: [ {required: true, message: '确认密码不能为空', trigger: "blur"}, { validator: validatePass, trigger: 'change' } ] } } }, methods: { handleCommand(command) { switch (command) { case 'a': // 打开修改密码窗口 this.handlePwd() break; case 'b': // 退出系统 this.handleLogout() break; default: break; } }, // 退出系统 handleLogout() { this.$store.dispatch('Logout').then(response => { if(response.message) { // 退出成功 // 回到登录页面 this.$router.push('/login') }else { this.$message({ message: resp.message, type: 'warning', duration: 500 // 弹出停留时间 }); } }) }, // 打开修改密码窗口 handlePwd(){ this.dialogFormVisible = true this.$nextTick(() => { this.$refs['ruleForm'].resetFields() }) }, // 修改密码 submitForm(formName) { this.$refs[formName].validate(valid => { if(valid) { console.log('校验成功') passwordApi.updatePwd(this.user.id, this.ruleForm.checkPass).then(response => { const resp = response.data // 不管失败还是成功,都进行提醒 this.$message({ message: resp.message, type: resp.flag ? 'success': 'warning' }) if(resp.flag) { // 更新成功, 退出系统,回到登录页面 this.handleLogout() // 关闭窗口 this.dialogFormVisible = false } }) }else { return false } }) } } } </script> <style scoped> .user-avatar { vertical-align:middle; width: 40px; height:40px; border-radius: 10px; } .logo{ vertical-align: middle; padding: 0px 10px 0 40px; } .company { position: absolute; color: white; } /* 下拉菜单 */ .el-dropdown { float: right; margin-right: 40px; } .el-dropdown-link { color: white; cursor: pointer; } </style>
权限一个 permission.js
代码如下
/** * 权限校验: * Vue Router中的 前置钩子函数 beforeEach(to, from, next) * 当进行路由跳转之前,进行判断 是否已经登录 过,登录过则允许访问非登录页面,否则 回到登录页 * * to: 即将要进入的目标路由对象 * from: 即将要离开的路由对象 * next: 是一个方法,可以指定路由地址,进行路由跳转 */ import router from './router' router.beforeEach((to, from, next) => { // 1. 获取token const token = localStorage.getItem('ssm-token') // const token = store.state.user.token console.log('token', token) if (!token) { // 1.1 如果没有获取到, // 要访问非登录页面,则不让访问,加到登录页面 /login if (to.path !== '/login') { next({ path: '/login' }) } else { // 请求登录页面 /login next() } } else { // 1.2 获取到token, // 1.2.1 请求路由 /login ,那就去目标路由 if (to.path === '/login') { next() } else { // 1.2.2 请求路由非登录页面,先在本地查看是否有用户信息, const userInfo = localStorage.getItem('ssm-user') // const userInfo = store.state.user.user console.log('userInfo', userInfo) if (userInfo) { // 本地获取到,则直接让它去目标路由 next() } else { console.log('获取用户信息') // 如果本地没有用户信息, 就通过token去获取用户信息, store.dispatch('GetUserInfo').then(response => { if (response.flag) { next() } else { next({ path: '/login' }) } }).catch(error => { }) } } } })
在main.js中进行拦截
-
在未登录情况下访问 http://localhost:8888/#/home ,会回到 登录
-
只有登录后,才可以访问首页
第六章 订单管理
6.1 列表查询
6.1.1 需求分析
订单管理主要针对订单信息进行管理,首先开发订单管理模块中的列表功能,包含条件查询、下拉框、日期功能、 数据列表、分页
6.1.2后端实现
6.1.2.1 创建ssm项目导入相关依赖
<dependencies> <!--web启动依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring整合mybatis依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- Swagger --> <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-devtools</artifactId> <scope>runtime</scope> </dependency> <!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--tomcat依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
6.1.2.2 编写配置类 application.properties
#mysql 数据库连接 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/smbms?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=root #mybatis日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #FieldStrategy 有三种策略: #IGNORED:0 忽略 #NOT_NULL:1 非 NULL,默认策略 #NOT_EMPTY:2 非空 #mybatis-plus.global-config.db-config.field-strategy=ignored #此为默认值,如果你的默认值和 mp 默认的一样,该配置可无 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0 #环境设置:dev、test、prod #全局设置主键生成策略 #mybatis-plus.global-config.db-config.id-type=auto mybatis-plus.configuration.map-underscore-to-camel-case=false #扫描包:别名 mybatis-plus.type-aliases-package=cn.zpark.pojo #加载xml mybatis-plus.mapper-locations=classpath:cn/zpark/mapper/*Mapper.xml
6.1.2.3 创建相关配置类
正在上传…重新上传取消
MpConfig
@EnableTransactionManagement @Configuration @MapperScan("cn.zpark.mapper") public class MpConfig { //乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } /*分页插件*/ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } //逻辑删除插件 @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); } }
SwaggerConfig
@Configuration//配置类 @EnableSwagger2 //swagger注解 public class SwaggerConfig { @Bean public Docket webApiConfig(){ return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() .paths(Predicates.not(PathSelectors.regex("/admin/.*"))) .paths(Predicates.not(PathSelectors.regex("/error.*"))) .build(); } private ApiInfo webApiInfo(){ return new ApiInfoBuilder() .title("API文档介绍") .description("本文档描述接口定义 ") .contact(new Contact("java", "http://ssm.com", "[email protected]")) .build(); } }
创建 MyMetaObjectHandler
@Component public class MyMetaObjectHandler implements MetaObjectHandler { //使用mp实现添加操作,这个方法执行 @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("creationDate", new Date(), metaObject); this.setFieldValByName("modifyDate", new Date(), metaObject); this.setFieldValByName("version",1,metaObject); this.setFieldValByName("deleted", 0, metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("modifyDate",new Date(),metaObject); } }
编写实体
@Data @TableName("smbms_bill") public class Bill { @ApiModelProperty(value = "主键ID") private Integer id;// '主键ID', @ApiModelProperty(value = "账单编码") private String billCode;// '账单编码', @ApiModelProperty(value = "商品名称") private String productName;//'商品名称', @ApiModelProperty(value = "商品描述") private String productDesc;//'商品描述', @ApiModelProperty(value = "商品单位") private String productUnit;//'商品单位', @ApiModelProperty(value = "商品数量") private Double productCount;// '商品数量', @ApiModelProperty(value = "商品总额") private Double totalPrice;// '商品总额', @ApiModelProperty(value = "是否支付(1:未支付 2:已支付)") private Integer isPayment;// '是否支付(1:未支付 2:已支付)',, @ApiModelProperty(value = "创建时间") private Date creationDate;// COMMENT '创建时间', @ApiModelProperty(value = "更新者(userId)") private Integer modifyBy;// '更新者(userId)', @ApiModelProperty(value = "供应商ID") private Integer providerId;//'供应商ID', @ApiModelProperty(value = "供应商名称") private String proName;//供应商名称 @ApiModelProperty(value = "创建时间",hidden = true) @TableField(value = "createdBy",fill = FieldFill.INSERT) private Integer createdBy;//'创建者(userId)' @ApiModelProperty(value = "修改时间",hidden = true) @TableField(value = "modifyDate",fill = FieldFill.INSERT_UPDATE) private Date modifyDate;// '更新时间', @Version @TableField(fill = FieldFill.UPDATE) @ApiModelProperty(value = "乐观锁的字段",hidden = true) private Integer version;//版本号 @TableLogic @TableField(fill = FieldFill.INSERT) @ApiModelProperty(value = "逻辑删除字段",hidden = true) private Integer deleted; }
编写条件封装类BillQuery去基础BaseRequest类
@Data public class BillQuery extends BaseRequest<Bill> { @ApiModelProperty(value = "商品名称,模糊查询") private String productName; @ApiModelProperty(value = "是否付款") private Integer isPayment; @ApiModelProperty(value = "供应商名称") private String proName; }
编写BaseRequest类,做分页条件
@Accessors(chain = true) @Data public class BaseRequest<T> implements Serializable { @ApiModelProperty(value = "页码", required = true) private long current; @ApiModelProperty(value = "每页显示多少条", required = true) private long size; /** * 封装分页对象 * @return */ @ApiModelProperty(hidden = true) // 不在swagger接口文档中显示 public IPage<T> getPage() { return new Page<T>().setCurrent(this.current).setSize(this.size); } }
编写ResultCode类封装
public interface ResultCode { public static Integer SUCCESS = 2000; //成功 public static Integer ERROR = 2001; //失败 }
创建R类返回结果
//统一返回结果的类 @Data public class R { @ApiModelProperty(value = "是否成功") private Boolean success; @ApiModelProperty(value = "返回码") private Integer code; @ApiModelProperty(value = "返回消息") private String message; @ApiModelProperty(value = "返回数据") private Map<String, Object> data = new HashMap<String, Object>(); //把构造方法私有 private R() {} //成功静态方法 public static R ok() { R r = new R(); r.setSuccess(true); r.setCode(ResultCode.SUCCESS); r.setMessage("成功"); return r; } //失败静态方法 public static R error() { R r = new R(); r.setSuccess(false); r.setCode(ResultCode.ERROR); r.setMessage("失败"); return r; } public R success(Boolean success){ this.setSuccess(success); return this; } public R message(String message){ this.setMessage(message); return this; } public R code(Integer code){ this.setCode(code); return this; } public R data(String key, Object value){ this.data.put(key, value); return this; } public R data(Map<String, Object> map){ this.setData(map); return this; } }
编写接口BillMapper
public interface BillMapper extends BaseMapper<Bill> { public List<Bill> pageAll(BillQuery billQuery); }
编写xml文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.zpark.mapper.BillMapper"> <select id="pageAll" resultType="bill" parameterType="billQuery"> SELECT *,p.proName FROM `smbms_bill` b,`smbms_provider` p WHERE b.`providerId`=p.id <if test="proName!=null"> and p.proName=#{proName} </if> <if test="isPayment!=0 and isPayment!=null"> and b.isPayment=#{isPayment} </if> <if test="productName!=null and productName!=''"> and b.productName like ("%"#{productName}"%") </if> limit #{current},#{size} </select> <insert id="save" parameterType="bill"> INSERT INTO `smbms_bill`(billCode,productName,productUnit,productCount,totalPrice,isPayment,providerId) VALUES (#{billCode},#{productName},#{productUnit},#{productCount},#{totalPrice},#{isPayment},#{providerId}) </insert> <select id="getById" resultType="bill"> SELECT *,p.proName FROM `smbms_bill` b,`smbms_provider` p WHERE b.`providerId`=p.id and b.id=#{id} </select> <update id="update" parameterType="bill"> update `smbms_bill` set billCode=#{billCode},productName=#{productName},productUnit=#{productUnit},totalPrice=#{totalPrice},isPayment=#{isPayment},providerId=#{providerId} where id=#{id} </update> </mapper>
编写BillService接口
public interface BillService extends IService<Bill> { public List<Bill> pageAll(BillQuery billQuery); public Integer selectCount(); }
编写实现类BillServiceImpl
//订单接口的实现类 @Service public class BillServiceImpl extends ServiceImpl<BillMapper,Bill> implements BillService { //查询所有 @Override public Page<Bill> pageAll(BillQuery billQuery) { //创建page对象 ,设置当前页和每页显示多少条 Page<Bill> pageBill = new Page<>(billQuery.getCurrent(),billQuery.getSize()); //创建封装条件的对象 QueryWrapper queryWrapper=new QueryWrapper(); //获取是否已付款的信息 Integer isPayment = billQuery.getIsPayment(); //获取商品名称 String productName = billQuery.getProductName(); //获取供应商名称 String proName = billQuery.getProName(); if (!StringUtils.isEmpty(productName)){ queryWrapper.eq("productName",isPayment); } if (!StringUtils.isEmpty(isPayment)){ queryWrapper.eq("isPayment",productName); } if (!StringUtils.isEmpty(proName)){ queryWrapper.eq("proName",proName); } //排序 queryWrapper.orderByDesc("billCode"); /* 0,10 1 1-1*10 10,20 2 2-1*10 */ //设置分页条件,通过第几页设置确定要查询从那条数据开始 billQuery.setCurrent(billQuery.getSize()*(billQuery.getCurrent()-1)); //获取总条数 pageBill.setTotal(selectCount()); //调用查询方法进行查询 List<Bill> all = baseMapper.findAll(billQuery); //将数据存储到分页中 pageBill.setRecords(all); return pageBill; } //查询订单总条数 @Override public Integer selectCount() { return baseMapper.selectCount(null); } }
编写控制器
@RestController @Api(description="用户登录") @RequestMapping("/bill") @CrossOrigin//解决跨域 public class BillController { @Autowired private BillService billService; @ApiOperation(value = "用户登录") @PostMapping("list/{current}/{limit}") public R findAll(@RequestBody(required = false) BillQuery billQuery){ //查询所有的数据,七张包含总条数,每一页显示的数据 Page<Bill> billPage = billService.pageAll(billQuery); //获取每一页显示的数据 List<Bill> records = billPage.getRecords(); //获取总条数 long total = billPage.getTotal(); return R.ok().data("total",total).data("rows",records); } }
6.1.3 前端实现
6.1.3.1编写api接口 bill.js
import request from "@/utils/request" //export function getList(current, limit, billQuery) { export function getList(billQuery) { return request({ // url: `/bill/list/${current}/${limit}`, url: `/bill/list`, method: 'post', data: billQuery }) }
6.1.3.2 编写内容
<template> <div class="app-container"> 供应商列表 <!--查询表单--> <el-form :inline="true" class="demo-form-inline"> <el-form-item> <el-input v-model="billQuery.productName" placeholder="商品名称"/> </el-form-item> <el-form-item label="供应商名称"> <el-select v-model="billQuery.proName" placeholder="请选择供应商"> <el-option label="区域一" value="shanghai"></el-option> <el-option label="区域二" value="beijing"></el-option> </el-select> </el-form-item> <el-form-item label="是否付款"> <el-select v-model="billQuery.isPayment" placeholder="请选择是否付款"> <el-option label="已付款" value="1"></el-option> <el-option label="未付款" value="2"></el-option> </el-select> </el-form-item> <el-button type="primary" icon="el-icon-search" @click="bill()" >查询</el-button> <el-button type="default" @click="resetData()">清空</el-button> </el-form> <!-- 表格 --> <el-table :data="list" border style="width: 100%"> <el-table-column fixed prop="billCode" label="订单编号" width="120"> </el-table-column> <el-table-column prop="productName" label="商品名称" width="140"> </el-table-column> <!-- <el-table-column prop="proName" label="供应商名称" width="220"> </el-table-column> --> <el-table-column prop="totalPrice" label="订单金额" width="120"> </el-table-column> <el-table-column label="是否付款" width="100"> <template slot-scope="scope"> {{ scope.row.isPayment===1?'已付款':'未付款' }} </template> </el-table-column> <el-table-column prop="creationDate" label="创建时间" width="250"> </el-table-column> <el-table-column fixed="right" label="操作" width="180"> <template slot-scope="scope"> <router-link :to="'/teacher/edit/'+scope.row.id"> <el-button type="text" size="small" icon="el-icon-share">查询</el-button> </router-link> <router-link :to="'/teacher/edit/'+scope.row.id"> <el-button type="text" size="small" icon="el-icon-edit">修改</el-button> </router-link> <el-button type="danger" size="small" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination :current-page="billQuery.current" :page-size="billQuery.size" :total="total" style="padding: 30px 0; text-align: center;" layout="total, prev, pager, next, jumper" @current-change="bill" /> </div> </template> <script> import { getList } from '@/api/bill' export default { data() { return { list:null, // page:1,//当前页 // limit:10,//每页记录数 total:0,//总记录数 billQuery:{ current:1,//当前页 size:10,//每页记录数 } //条件封装对象 } }, created(){ this.bill() } , methods: { bill(current=1){ this.billQuery.current=current //getList(this.page,this.limit,this.billQuery).then(resp => { getList(this.billQuery).then(resp => { console.log("------------",resp) this.list=resp.data.rows; this.total = resp.data.total }) } , resetData() {//清空的方法 //表单输入项数据清空 this.billQuery = {} //查询所有订单数据 this.getList() }, } } </script>
6.1.3.3路由配置
{ path: '/bill', component: Layout, children: [{ path: '/', component: Bill, meta: { title: '订单管理' } }] },
在AppMain中添加视图渲染
<template> <div class="main"> 这里是展示数据地方 <router-view></router-view> </div> </template> <script> </script>
6.2添加修改订单
6.2.1后端实现
6.2.1.1
在接口中添加修改的方法
//修改订单 public void update(Bill bill);
xml编写
<update id="update" parameterType="bill"> update `smbms_bill` set billCode=#{billCode},productName=#{productName},productUnit=#{productUnit},totalPrice=#{totalPrice},isPayment=#{isPayment},providerId=#{providerId} where id=#{id} </update>
义务口的编写
//修改订单 public void update(Bill bill);
实现的类
@Override public void update(Bill bill) { baseMapper.update(bill); }
控制器的编写
@ApiOperation(value = "订单的修改") @GetMapping("update") public R update(@PathVariable Bill bill){ billService.update(bill); return R.ok(); }
6.2.前端实现
<el-table-column label="操作" width="150"> <template slot-scope="scope"> <el-button size="mini" @click="handleEdit(scope.row.id)">编辑</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.row.id)">删除</el-button> </template> </el-table-column>
<!-- 弹出新增窗口 title 窗口的标题 :visible.sync 当它true的时候,窗口会被弹出 --> <el-dialog title="订单编辑" :visible.sync="dialogFormVisible" width="500px"> <el-form ref="billForm" label-width="100px" label-position="right" style="width: 400px;" :model="eidtBill"> <el-form-item label="商品名称" prop="productName" > <el-input v-model="eidtBill.productName" ></el-input> </el-form-item> <el-form-item label="供应商名称"> <el-select v-model="eidtBill.providerId" placeholder="供应商名称"> <!-- 不要忘记 payTypeOptions 绑定到data中 --> <el-option v-for="option in prolist" :key="option.id" :label="option.proName" :value="option.id" ></el-option> </el-select> </el-form-item> <!-- <el-form-item label="供应商名称" prop="proName" > <el-input v-model="eidtBill.proName" ></el-input> </el-form-item> --> <el-form-item label="订单金额" prop="totalPrice" > <el-input v-model="eidtBill.totalPrice" ></el-input> </el-form-item> <el-form-item label="是否付款"> <el-select v-model="eidtBill.isPayment==1?'已付款':'未付款'" placeholder="请选择是否付款"> <el-option label="已付款" value="1"></el-option> <el-option label="未付款" value="2"></el-option> </el-select> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="updateData()">确 定</el-button> </div> </el-dialog>
第七章阿里云-对象存储OSS
官方参考文档:对象存储 OSS-阿里云帮助中心
对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任
意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
开通对象存储OSS服务
-
访问阿里云 https://www.aliyun.com/ ,在右上角点击登录。
-
登录后,进入控制台
-
搜索 OSS , 找到 对象存储OSS
-
点击 立即开通
-
勾选,点击 立即开通
-
开通成功, 点击 管理控制台
购买 OSS 资源包
注意 开通 OSS 服务后,默认的计费方式是按量付费。如果想降低 OSS 费用,建议您购买资源包 。
-
点击 购买资源包
-
选择自己需要的资源包,学习按下图买即可。
-
前往支付
-
支付成功,回到管理控制台,查看得到购买记录
创建与删除存储空间 ( Bucket )
存储空间(Bucket)是用于存储对象(Object)的容器,可以存储若干文件。所以在上传任何文件到OSS之前,您必
须先创建存储空间。
创建 Bucket
进入 对象存储OSS 管理中心,阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
-
打开创建Bucket对话框。单击Bucket列表,之后单击创建Bucket。
-
输入 Bucket 名称、和区域、存储类型:标签存储,其他默认就行,详细可见官方文档
注意:Bucket 创建成功后,您所选择的 存储类型、区域 不支持变更。
-
创建成功
-
查看 Bucket 相关信息,后面会使用到。
Endpoint:上传文件时使用
Bucket域名:查看文件时使用
删除 Bucket
-
如果需要删除 Bucket ,在 Bucket列表页点击 要删除的Bucket名称,进入详情页。
设置 Bucket 公共读权限
在页面上传文件
-
如下图,进入 Bucket ,找到 文件管理 ,点击 上传文件
设置 Bucket 公共读权限
-
点击图片文件名 Logo.png ,打开的右侧有个 URL,URL带上签名参数可以浏览器直接访问,会自动下载。
但是如果不带签名无法访问,提示如下没有权限:
创建 RAM 账号-AccessKey
阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常
运维,请登录 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 创建RAM账号。
创建用户
-
访问 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 ,输入登录名称、显示名称,勾选 编程访问 ,点击 确定
-
创建成功后,将查看到的 AccessKeyID 、AccessKeySecret **复制保存下来,在Java代码中要使用它操作
OSS,不然页面关闭后将无法再次获取到此信息。如果没有记录下来, 创建新的AccessKey
用户授权
对指定用户添加管理对象存储服务(OSS)服务。
-
访问 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 点击 添加权限
OSS 上传与删除图片文件
参考官方提供的 SDK: OSSJavaSDK兼容性和示例代码_对象存储-阿里云帮助中心
3758a8YW2zNr
JDK (Java Development Kit):Java 语言的软件开发工具包
SDK (Software Development Kit):为第三方开发者提供特定的软件开发工具包
添加 OSS SDK 依赖
<!-- aliyun oss --> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.8.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dependency>
创建OSS相关配置类 Properties
-
在 util 下创建一个 Properties 类,
统一管理整个项目在 application.yml 中自定义配置信息,比如:有阿里云相关的配置信息。
package cn.zpark.utils; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.io.Serializable; @Setter @Getter @Component @ConfigurationProperties(prefix = "zparkoss.blog") public class Properties implements Serializable { // 会将 zparkoss.oss-cn-shenzhen.aliyuncs.com 绑定到 AliyunProperties 对象上。 private AliyunProperties aliyun; }
-
在 util 下创建一个 阿里云配置信息类:
AliyunProperties
package cn.zpark.utils; import lombok.Data; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.io.Serializable; @Data public class AliyunProperties implements Serializable { /** * 阿里云地域端点 */ private String endpoint; private String accessKeyId; private String accessKeySecret; /** * 存储空间名称 */ private String bucketName; /** * Bucket域名,访问文件时作为URL前缀 */ private String bucketDomain; }
配置 OSS 相关信息
在创建一个application.yml 文件在文件中添加如下配置:
zparkoss: blog: # 阿里云配置 aliyun: endpoint: http://oss-cn-shenzhen.aliyuncs.com # OSS 端点,根据自己地域替换 accessKeyId: LTAI5tBZzwox73f4X2dvBrrv # 根据自己的配置 accessKeySecret: zWPePjWjHDBgHmg8Tdwal6HcRUDFWR # 根据自己的配置 bucketName: zparkoss # 存储空间名称 # Bucket域名,访问文件时作为URL前缀,注意前面加上 https 和结尾加上 / bucketDomain: https://zparkoss.oss-cn-shenzhen.aliyuncs.com/
工具类 AliyunUtil
-
在 util 添加阿里云工具类 AliyunUtil
涉及功能:上传、删除图片文件
package cn.zpark.utils; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.comm.ResponseMessage; import com.aliyun.oss.model.PutObjectResult; import lombok.Data; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.time.DateFormatUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.util.UUID; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.comm.ResponseMessage; import com.aliyun.oss.model.PutObjectResult; import org.springframework.web.multipart.MultipartFile; import java.util.Date; import java.util.UUID; /** * 阿里云工具类 */ @Data @Component public final class AliyunUtil { @Autowired private Properties properties; /** * 上传图片文件 * * @param file MultipartFile文件对象 * @return */ public R uploadFileToOss(MultipartFile file) { // 上传 // 上传文件所在目录名,当天上传的文件放到当天日期的目录下。 String folderName =DateFormatUtils.format(new Date(), "yyyyMMdd"); // 保存到 OSS 中的文件名,采用 UUID 命名。 String fileName = UUID.randomUUID().toString().replace("-", ""); // 从原始文件名中,获取文件扩展名 String fileExtensionName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); // 文件在 OSS 中存储的完整路径 String filePath = folderName + "/" + fileName + fileExtensionName; OSS ossClient = null; try { // 获取 OSS 客户端实例 ossClient = new OSSClientBuilder().build(properties.getAliyun().getEndpoint(),properties.getAliyun().getAccessKeyId(),properties.getAliyun().getAccessKeySecret()); // 上传文件到OSS 并响应结果 PutObjectResult putObjectResult = ossClient.putObject(properties.getAliyun().getBucketName(), filePath, file.getInputStream()); ResponseMessage response = putObjectResult.getResponse(); if(response == null) { // 上传成功 // 返回上传文件的访问完整路径 System.out.println("____________"+properties.getAliyun().getBucketDomain() + filePath ); return R.ok().data("",properties.getAliyun().getBucketDomain() + filePath ); }else { // 上传失败,OOS服务端会响应状态码和错误信息 String errorMsg = "响应的错误状态码是【" + response.getStatusCode() +"】," + "错误信息【"+response.getErrorResponseAsString()+"】"; return R.error(); } } catch (Exception e) { return R.error(); } finally { if (ossClient != null) { // 关闭OSSClient。 ossClient.shutdown(); } } } /** * 根据文件url删除 * @param fileUrl */ public R delete(String fileUrl) { // 去除文件 url 中的 Bucket域名 String filePath = fileUrl.replace(properties.getAliyun().getBucketDomain(), ""); OSS ossClient = null; try { ossClient = new OSSClientBuilder().build(properties.getAliyun().getEndpoint(),properties.getAliyun().getAccessKeyId(),properties.getAliyun().getAccessKeySecret()); // 删除 ossClient.deleteObject(properties.getAliyun().getBucketName(), filePath); return R.ok(); } catch (Exception e) { return R.error(); }finally { if (ossClient != null) { ossClient.shutdown(); } } } }
创建文件管理控制层
创建 FileController类
package cn.zpark.controller; import cn.zpark.utils.AliyunUtil; import cn.zpark.utils.R; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; /** * 图片文件控制器 */ @Api(value = "文件管理接口", description = "文件管理接口,上传或删除图片文件") @RequestMapping("/file") @RestController public class FileController { @Autowired private AliyunUtil aliyunUtil; @ApiOperation("上传文件到OSS") @PostMapping("/upload") public R upload(@RequestParam("file") MultipartFile file) { System.out.println(file); R r = aliyunUtil.uploadFileToOss(file); return R.ok(); } @ApiOperation("删除OOS服务器的文件") @ApiImplicitParam(name="fileUrl", value="要删除的文件URL", required=true) @DeleteMapping("/delete") public R delete(@RequestParam(value = "fileUrl", required = false) String fileUrl) { return aliyunUtil.delete(fileUrl); } }
最后进行测试
第八章:添加订单
1后端的实现
创建接口
//添加的功能 public void add(Bill bill);
业务接口‘
//添加的功能 public void add(Bill bill);
接口实现类
@Override public void add(Bill bill) { baseMapper.add(bill); }
控制器
@ApiOperation(value = "用户登录") @PostMapping("add") public R add(@RequestBody Bill bill){ System.out.println("添加----"+bill); return R.ok(); }
2前端实现
定义的接口
// 上传图片 uploadImg(data = {}) { return request({ url: `/file/upload`, method: 'post', data }) }, // 删除图片 /article/file/delete?fileUrl=http://xxxxx deleteImg(imageUrl) { return request({ url: `/file/delete`, method: 'delete', params: { 'fileUrl': imageUrl } }) }, add(bill) { return request({ url: `/bill/add`, method: 'post', data: bill }) },
<el-upload class="avatar-uploader" action="" :show-file-list="false" :http-request="uploadMainImg"> <img v-if="imageUrl" :src="imageUrl" class="avatar"> <i v-else class="el-icon-plus avatar-uploader-icon"></i> </el-upload>
// 上传图片, file 上传的图片对象 uploadMainImg(file) { console.log('file', file) const data = new FormData() data.append('file', file.file) bill.uploadImg(data).then(response => { console.log("-------------------",response) // 回显图片 this.formData.imageUrl = response.data }).catch(error => { this.$message({ type: 'error', message: '上传失败' }) }) },
<template> <div class="app-container"> 供应商列表 <el-dialog title="订单编辑" :visible.sync="dialogFormVisible" width="500px"> <el-form ref="billForm" label-width="100px" label-position="right" style="width: 400px;" :model="bill"> <el-form-item label="商品名称" prop="productName" > <el-input v-model="bill.productName" ></el-input> </el-form-item> <el-form-item> <el-upload class="avatar-uploader" action="" :show-file-list="false" :http-request="uploadMainImg"> <img v-if="imageUrl" :src="imageUrl" class="avatar"> <i v-else class="el-icon-plus avatar-uploader-icon"></i> </el-upload> </el-form-item> <el-form-item label="订单金额" prop="totalPrice" > <el-input v-model="bill.totalPrice" ></el-input> </el-form-item> <el-form-item label="是否付款"> <el-select v-model="bill.isPayment" placeholder="请选择是否付款"> <el-option label="已付款" value="1"></el-option> <el-option label="未付款" value="2"></el-option> </el-select> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="add()">确 定</el-button> </div> </el-dialog> </div> </template> <script> import bill from '@/api/bill' export default { data() { return { imageUrl: '', formData: { // 提交表单数据 }, dialogFormVisible: true, //控制对话框 bill:{} } }, methods: { add(){ bill.add(this.bill).then(resp=>{ }) }, // 上传图片, file 上传的图片对象 uploadMainImg(file) { console.log('file', file) const data = new FormData() data.append('file', file.file) bill.uploadImg(data).then(response => { console.log("-------------------",response) // 回显图片 this.formData.imageUrl = response.data }).catch(error => { this.$message({ type: 'error', message: '上传失败' }) }) }, } } </script> <style> .avatar-uploader .el-upload { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; } .avatar-uploader .el-upload:hover { border-color: #409EFF; } .avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 178px; height: 178px; line-height: 178px; text-align: center; } .avatar { width: 178px; height: 178px; display: block; } </style>
{ path: '/edit', component: Layout, children: [{ path: '/', component: Edit, meta: { title: '订单管理' } }] }, 标签:return,管理系统,value,超市,订单,import,response,data,public From: https://www.cnblogs.com/chengfaxing/p/16997908.html