超市订单管理系统设计与实现
管理系统实战-前后端分离
第一章 项目需求分析和技术架构
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/cli@3.10.0
- 安装成功后,在命令行可以使用 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", "1123@qq.com"))
.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: '订单管理' }
}]
},