文章目录
1.单体前后端项目上传
1.上传流程
这样有利于保护自己的账号和密码不被泄露,通过账号密码生成的防伪笔名进行验证是否正确
2. BuckName 和EndPoint
3. AccessKey 和Access Secret(创建RAM(Resource Access Manage)的子账号,然后可以获得Accesskey和Acess Secret)
3.根据创建的子账号分配OSS的所有权限(可以对文件进行上传,下载,删除的权限)
4.采用上传的方式(服务器直传,服务器得签名后后端直传,前端直传)
前端直传:不好,用户可以直接f12找到我们的endpoint,bucketName,和重要的Acesskey,AccessSecret,不安全,导致我们的信息泄露。
服务器直传:会造成性能瓶颈,给服务器造成压力。
服务器得签名后后端直传:这种方法可以,可以加密我们的签名之后进行上传。如果想了解用户上传了什么东西可以设计 上传回调
5. 出现跨域问题解决
引入依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.0</version>
</dependency>
<dependency>
6.前端实现code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>oss测试上传</title>
<script src="static/js/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<div id="app">
<!-- <form action="/upload/oss" method="post" enctype="multipart/form-data">
<input type="file" name="file" value="选择文件上传">
<input type="submit" name="提交"/>
</form> -->
<el-upload
draggable="true"
class="upload-demo"
:action="obj.host"
:before-upload="ossPolicy"
:on-success="onsuccess"
//上传时绑定的数据
:data="obj"
:limit="2"
:file-list="fileList"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</div>
<script src="static/js/axios.min.js"></script>
<script type="text/javascript ">
var vm=new Vue({
el:"#app",
data:{
fileList: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}],
obj:{
OSSAccessKeyId:"",
policy:"",
signature:"",
key:"",
host:"",
dir:"",
}
},
methods: {
ossPolicy(file){
// 上传前,获取服务器给的签名
axios({
url:"http://localhost:8080/oss/policy",
method:"get",
}).then(res=>{
console.log("come in ---------",res.data)
this.obj.OSSAccessKeyId = res.data.accessId;
this.obj.policy = res.data.policy;
this.obj.dir = res.data.dir;
this.obj.signature = res.data.signature;
//传到oss的服务器地址
this.obj.host = res.data.host;
this.obj.key =res.data.dir + "${filename}";
})
},
//文件上传成功时调用
onsuccess(response, file, fileList){
console.log("response",response)
console.log(file)
},
}
})
</script>
</body>
</html>
6.1 单个文件上传
<template>
<div>
<el-upload
:action="dataObj.host"
:data="dataObj"
list-type="picture"
:multiple="false" :show-file-list="showFileList"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt="">
</el-dialog>
</div>
</template>
<script>
import {policy} from './policy'
import { getUUID } from '@/utils'
export default {
name: 'singleUpload',
props: {
value: String
},
computed: {
imageUrl() {
return this.value;
},
imageName() {
if (this.value != null && this.value !== '') {
return this.value.substr(this.value.lastIndexOf("/") + 1);
} else {
return null;
}
},
fileList() {
return [{
name: this.imageName,
url: this.imageUrl
}]
},
showFileList: {
get: function () {
return this.value !== null && this.value !== ''&& this.value!==undefined;
},
set: function (newValue) {
}
}
},
data() {
return {
dataObj: {
policy: '',
signature: '',
key: '',
ossaccessKeyId: '',
dir: '',
host: '',
// callback:'',
},
dialogVisible: false,
url:'',
};
},
methods: {
emitInput(val) {
this.$emit('input', val)
},
handleRemove(file, fileList) {
this.emitInput('');
},
handlePreview(file) {
this.dialogVisible = true;
},
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy().then(response => {
console.log("响应的数据",response);
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
_self.dataObj.key = response.data.dir +getUUID()+'_${filename}';
_self.dataObj.dir = response.data.dir;
_self.dataObj.host = response.data.host;
console.log("响应的数据222。。。",_self.dataObj);
resolve(true)
}).catch(err => {
reject(false)
})
})
},
handleUploadSuccess(res, file) {
console.log("上传成功...")
this.showFileList = true;
this.fileList.pop();
this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
this.url = this.fileList[0].url;
this.emitInput(this.fileList[0].url);
}
}
}
</script>
<style>
</style>
可以用一个url变量接收我们的上传之后成功的图片路径,点击保存的时间可以将这个url传到数据库中去,前端在直接通过单向绑定:src =“显示数据库中保存的url”
6.2多个文件上传
<template>
<div>
<el-upload
action="http://+`你的bucketName` + '.' +'你的endpoint'"
:data="dataObj"
:list-type="listType"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
:limit="maxCount"
:on-exceed="handleExceed"
:show-file-list="showFile"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt />
</el-dialog>
</div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from '@/utils'
export default {
name: "multiUpload",
props: {
//图片属性数组
value: Array,
//最大上传图片数量
maxCount: {
type: Number,
default: 30
},
listType:{
type: String,
default: "picture-card"
},
showFile:{
type: Boolean,
default: true
}
},
data() {
return {
dataObj: {
policy: "",
signature: "",
key: "",
ossaccessKeyId: "",
dir: "",
host: "",
uuid: ""
},
dialogVisible: false,
dialogImageUrl: null
};
},
computed: {
fileList() {
let fileList = [];
for (let i = 0; i < this.value.length; i++) {
fileList.push({ url: this.value[i] });
}
return fileList;
}
},
mounted() {},
methods: {
emitInput(fileList) {
let value = [];
for (let i = 0; i < fileList.length; i++) {
value.push(fileList[i].url);
}
this.$emit("input", value);
},
handleRemove(file, fileList) {
this.emitInput(fileList);
},
handlePreview(file) {
this.dialogVisible = true;
this.dialogImageUrl = file.url;
},
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy()
.then(response => {
console.log("这是什么${filename}");
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
_self.dataObj.key = response.data.dir +getUUID()+"_${filename}";
_self.dataObj.dir = response.data.dir;
_self.dataObj.host = response.data.host;
resolve(true);
})
.catch(err => {
console.log("出错了...",err)
reject(false);
});
});
},
handleUploadSuccess(res, file) {
this.fileList.push({
name: file.name,
// url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替换${filename}为真正的文件名
url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}",file.name)
});
this.emitInput(this.fileList);
},
handleExceed(files, fileList) {
this.$message({
message: "最多只能上传" + this.maxCount + "张图片",
type: "warning",
duration: 1000
});
}
}
};
</script>
<style>
</style>
7.后端code实现
package com.qfedu.fmmall.controller;/*
**
*@author SmallMonkey
*@Date 2023/2/10 13:07
*
*
**/
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@CrossOrigin
@RequestMapping("/oss")
public class OssController {
@RequestMapping("/policy")
public Map<String,String> policy(){
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessId = "LTAI5tRNby21a72dAhf4NbPa";
String accessKey = "LTWOauYIuCkNF2O9zgU0Mm7HCuV5BE";
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "oss-cn-hangzhou.aliyuncs.com";
// 填写Bucket名称,例如examplebucket。
String bucket = "fmmallrebuild";
// 填写Host地址,格式为https://bucketname.endpoint。
String host = "https://" + bucket + "." + endpoint;
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
// String callbackUrl = "https://192.168.0.0:8888";
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
//可以加上当前的日期
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dir = simpleDateFormat.format(new Date()) + "/";
//String objectName = dir + UUID.randomUUID().toString()+".png";
// 创建ossClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
Map<String, String> respMap = new LinkedHashMap<String, String>();
respMap.put("accessId", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
return respMap;
//服务进行上线之后,有一个外网可以访问的地址可以设置回调
/*
JSONObject jasonCallback = new JSONObject();
jasonCallback.put("callbackUrl", callbackUrl);
jasonCallback.put("callbackBody",
"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
respMap.put("callback", base64CallbackBody);
JSONObject ja1 = JSONObject.fromObject(respMap);*/
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return null;
}
}
2.SpringCloud形式OSS上传
1.引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
2.后端实现code
@RestController
public class OssController {
@Autowired
OSS ossClient;
@Value ("${spring.cloud.alicloud.oss.endpoint}")
String endpoint ;
@Value("${spring.cloud.alicloud.oss.bucket}")
String bucket ;
@Value("${spring.cloud.alicloud.access-key}")
String accessId ;
@Value("${spring.cloud.alicloud.secret-key}")
String accessKey ;
@RequestMapping("/oss/policy")
public Map<String, String> policy(){
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format; // 用户上传文件时指定的前缀。
Map<String, String> respMap=null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap= new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
return respMap;
}
}
3.前端实现code
2. 商品管理系统OSS实现图片显示和图片上传
1.前端Code
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js">
</script>
</head>
<body>
<div class="fillcontain" id="app">
<template>
<!-- <el-alert
title="商品管理页面"
type="success"
show-icon>
</el-alert>
-->
</template>
<h2 class="ui teal header" style="text-align: center;margin-top: 10px;">欢迎来到商品管理页面</h2>
<el-form :model="dataForm" class="search_container" label-width="100px" @keyup.enter.native="submit(1)">
<el-row>
<el-col :span="8">
<el-form-item label="农产品类别">
<!-- <el-select filterable style="width: 220px;" v-model="dataForm.categoryName" default-first-option
filterable allow-create clearable size="small" placeholder="选择商品一级分类"
@change="changeSubCategory" >
<el-option v-for="(item,index) in this.parentCategoryList" :key="index" :label="item.label"
:value="item.value" />
</el-select> -->
<el-cascader filterable :show-all-levels="false" size="small" clearable
v-model="dataForm.categoryIds" :options="options" :props="props" @change="handleChange"
@getCheckedNodes="getCheckedNodes">
<!-- 自定义显示的列 -->
<template slot-scope="{ node,data }">
<span>{{data.categoryId}}、{{ data.categoryName}}</span>
</template>
</el-cascader>
</el-form-item>
</el-col>
<!-- <el-col :span="5">
<el-form-item label="二级">
<el-select style="width: 120px;" filterable allow-create @change="change" placeholder="二级分类"
clearable size="small" v-model="dataForm.categoryName1">
<el-option v-for="(item,index) in this.subOptions" :key="index" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col> -->
<el-col :span="8">
<el-form-item label="商品名称" label-width="120px">
<el-input v-model="dataForm.product_name" placeholder="输入商品名称" clearable size="small">
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="商品的状态">
<el-select v-model="dataForm.isup" clearable size="small">
<el-option label="上架" value="1" style="color: teal;"> </el-option>
<el-option label="下架" value="0" style="color: red;"> </el-option>
</el-select>
<!-- <el-switch style="display: block" v-model="dataForm.isup" active-value="1"
inactive-value="0" active-color="#13ce66"
inactive-color="#ff4949" active-text="上架" inactive-text="下架" @change="changeProductStatus(scope.row)">
</el-switch>
-->
</el-form-item>
</el-col>
<el-col :span="18">
<el-form-item label="商品上架日期">
<el-date-picker v-model="dataForm.submistrdte" type="date" placeholder="开始日期"
value-format="yyyy-MM-dd" clearable size="small">
</el-date-picker>
<span class="spanline"> - </span>
<el-date-picker v-model="dataForm.submienddte" type="date" placeholder="结束日期"
value-format="yyyy-MM-dd" @blur='dataBlur' clearable size="small">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6" class="text_center">
<el-button type="primary" icon="el-icon-search" @click="intDataList(1)" size="small"></el-button>
<el-button icon="el-icon-refresh" @click="resetForm" size="small"></el-button>
<el-button type="success" size="small" @click="addHandle">新增商品</el-button>
</el-col>
</el-row>
</el-form>
<!-- 查询结果列表 -->
<div class="listbox">
<!-- 列表 -->
<el-table ref="multipleTable" :data="dataList" border :fit="true" style="width: 100%" disabled>
<el-table-column sortable type="index" label="序号" header-align="center" width="45"></el-table-column>
<el-table-column v-if="false" sortable prop="product_id" label="商品id" show-overflow-tooltip
min-width="100" disabled>
<template slot-scope="scope">
<span> {{ scope.row.product_id || '--' }} </span>
</template>
</el-table-column>
<el-table-column sortable prop="product_name" label="商品名称" show-overflow-tooltip min-width="100"
disabled>
<template slot-scope="scope">
<span> {{ scope.row.product_name || '--' }} </span>
</template>
</el-table-column>
<el-table-column sortable prop="sell_price" label="商品销售价格" show-overflow-tooltip min-width="100"
disabled>
<template slot-scope="scope">
<span> {{ scope.row.sell_price || '--' }} </span>
</template>
</el-table-column>
<el-table-column sortable prop="sku_name" label="商品套餐名" show-overflow-tooltip min-width="80" disabled>
<template slot-scope="scope">
<span> {{ scope.row.sku_name || '--' }} </span>
</template>
</el-table-column>
<el-table-column sortable prop="untitled" label="商品套餐" show-overflow-tooltip min-width="180" disabled>
<template slot-scope="scope">
<span> {{ scope.row.untitled || '--' }} </span>
</template>
</el-table-column>
<el-table-column sortable prop="url" label="商品图片显示" show-overflow-tooltip min-width="100"
disabled>
<template slot-scope="scope">
<!-- 通过oss显示图片 -->
<img width="80px" height="25px" :src="scope.row.url" style="border-radius: 5px;">
</template>
</el-table-column>
<el-table-column sortable prop="category_name" label="商品分类名称" show-overflow-tooltip min-width="100">
<template slot-scope="scope"> {{ scope.row.category_name || '--' }} </template>
</el-table-column>
<el-table-column prop="isup" label="商品的状态" show-overflow-tooltip min-width="130" sortable>
<template slot-scope="scope">
<!-- <span v-if="scope.row.isup==1" style="color:teal">上架</span>
<span v-if="scope.row.isup==0" style="color:red">下架</span> -->
<el-switch style="display: block" v-model="scope.row.isup" active-value="1" inactive-value="0"
active-color="#13ce66" inactive-color="#ff4949" active-text="上架" inactive-text="下架"
@change="changeProductStatus(scope.row)">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="sold_num" label="销售数量" show-overflow-tooltip min-width="100" sortable>
<template slot-scope="scope"> {{ scope.row.sold_num || '--' }} </template>
</el-table-column>
<el-table-column prop="content" label="商品说明" show-overflow-tooltip min-width="110" sortable>
<template slot-scope="scope">
<span>{{ scope.row.content || '--' }}</span>
</template>
</el-table-column>
<el-table-column prop="create_time" label="商品创建时间" show-overflow-tooltip min-width="100" sortable>
<template slot-scope="scope"> {{ scope.row.create_time || '--' }} </template>
</el-table-column>
<el-table-column prop="update_time" label="商品更新时间" show-overflow-tooltip min-width="100" sortable>
<template slot-scope="scope"> {{ scope.row.update_time || '--' }} </template>
</el-table-column>
<el-table-column label="操作" show-overflow-tooltip min-width="180" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small" @click="copyHandle(scope.row)">复制</el-button>
<el-button type="text" size="small" @click="UpdateHandle(scope.row)">修改</el-button>
<!-- <el-button
type="text"
size="small"
@click="saveHandle(scope.row, scope.$index, scope.row.id)"
v-if="!scope.row.disabled"
>保存</el-button
> -->
<el-button type="text" size="small" @click="deleteHandle(scope.row, scope.$index)">删除
</el-button>
</template>
</el-table-column>
<el-button type="danger"> 新增</el-button>
</el-table>
<!-- 分页 -->
<el-row>
<el-col :span="24">
<div class="pagination">
<!-- 分页 -->
<el-pagination background @size-change="sizeChangeHandle" @current-change="currentChangeHandle"
:current-page="page.pageIndex" :page-sizes="[10, 20, 50, 100]"
:page-size="page.pageformSize" :total="page.totalPage"
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
</div>
</el-col>
</el-row>
</div>
<br><br><br><br><br>
<el-dialog title="商品信息修改" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="商品名称" :label-width="formLabelWidth">
<el-input v-model="form.product_name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品价格" :label-width="formLabelWidth">
<el-input v-model="form.sell_price" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品套餐名" :label-width="formLabelWidth">
<el-input v-model="form.sku_name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品套餐" :label-width="formLabelWidth">
<el-input v-model="form.untitled" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品图片" :label-width="formLabelWidth">
<!-- <el-upload ref="doctypeCrfile" class="upload-demo" drag :limit="3"
action="http://localhost:8080/upload/uploadFile1" multiple :file-list="fileList"
:on-success="handleSucess" :show-file-list="true" v-model="form.pics" :auto-upload="false"
:before-upload="beforeAvatarUpload" list-type="picture">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
</el-upload> -->
<el-upload
draggable="true"
class="upload-demo"
:action="obj.host"
:before-upload="ossPolicy"
:on-success="handleUploadSuccess"
//上传时绑定的数据
:data="obj"
:limit="1"
:file-list="fileList"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="form.url" alt="">
</el-dialog>
</el-form-item>
<el-form-item label="商品分类名称">
<!-- <el-select filterable v-model="form.category_id" default-first-option filterable allow-create
clearable size="small" placeholder="" @change="changeSubCategory" >
<el-option v-for="(item,index) in this.parentCategoryList" :key="index" :label="item.label"
:value="item.value" />
</el-select> -->
<el-cascader clearable v-model="form.categoryIds" :options="options" :props="props"
@change="handleChange">
<template slot-scope="{ node,data }">
<span>{{data.categoryId}}、{{ data.categoryName}}</span>
</template>
</el-cascader>
</el-form-item>
</el-form-item>
<!-- -->
<el-form-item label="商品的状态" :label-width="formLabelWidth">
<!-- <el-select v-model="form.isup" autocomplete="off">
<el-option label="上架" value="1" style="color: teal;" checked></el-option>
<el-option label="下架" value="0" style="color: red;"></el-option>
</el-select> -->
<el-switch style="display: block" v-model="form.isup" active-value="1" inactive-value="0"
active-color="#13ce66" inactive-color="#ff4949" active-text="上架" inactive-text="下架">
</el-switch>
</el-form-item>
<el-form-item label="商品说明" :label-width="formLabelWidth">
<el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="form.content" autocomplete="off">
</el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="updateProduct(form)" :disabled="confirm">修 改</el-button>
</div>
</el-dialog>
<!-- 商品信息的保存 -->
<el-dialog title="商品添加" :visible.sync="dialogFormVisibleSave">
<el-steps :space="800" :active="active" finish-status="success">
<el-step title="步骤 1">
</el-step>
<el-step title="步骤 2"></el-step>
</el-steps>
<el-form :label-position="labelPosition" :model="form" v-show="show">
<h2>商品信息添加 </h2>
<el-form-item label="商品名称" :label-width="formLabelWidth">
<el-input v-model="form.product_name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品图片" :label-width="formLabelWidth">
<!-- <el-upload ref="doctypeCrfile" class="upload-demo" drag :limit="3"
action="http://localhost:8080/upload/uploadFile1" multiple :file-list="fileList"
:on-success="handleSucess" :show-file-list="true" v-model="form.url" :auto-upload="false"
list-type="picture" :before-upload="beforeAvatarUpload">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
</el-upload> -->
<el-upload
draggable="true"
class="upload-demo"
:action="obj.host"
:before-upload="ossPolicy"
:on-success="handleUploadSuccess"
//上传时绑定的数据
:data="obj"
:limit="2"
:file-list="fileList"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt="">
</el-dialog>
</el-form-item>
<el-form-item label="商品分类名称" :label-width="formLabelWidth">
<!--
<el-cascader
:props="defaultParams"
:options="this.parentCategoryList"
v-model="subOptions1"
@change="changeSubCategory">
</el-cascader> -->
<!--
<el-select clearable v-model="form.category_id" placeholder="请选择活动区域">
<el-option v-for="item in this.parentCategoryList" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select> -->
<el-cascader clearable v-model="form.categoryIds" :options="options" :props="props"
@change="handleChange"></el-cascader>
</el-form-item>
<!-- -->
<el-form-item label="商品的状态" :label-width="formLabelWidth">
<!-- <el-select v-model="form.isup" autocomplete="off">
<el-option label="上架" value="1" style="color: teal;" checked></el-option>
<el-option label="下架" value="0" style="color: red;"></el-option>
</el-select> -->
<el-switch style="display: block" v-model="form.isup" active-value="1" inactive-value="0"
active-color="#13ce66" inactive-color="#ff4949" active-text="上架" inactive-text="下架">
</el-switch>
</el-form-item>
<el-form-item label="商品说明" :label-width="formLabelWidth">
<el-input type="textarea" :rows="2" placeholder="请输入内容" v-model="form.content" autocomplete="off"
@input="passInput"></el-input>
</el-form-item>
<p style="margin-left:66px;font-size:9px;text-align:center"> 还可以输入<strong
style="color:red">{{numRemark}}</strong>个字 </p>
</el-form>
<el-form :label-position="labelPosition" :model="form" v-show="!show">
<h2>商品属性信息添加 </h2>
<el-form-item label="商品套餐名称">
<el-input v-model="form.sku_name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="商品口味">
<el-input v-model="form.untitled">
</el-input>
</el-form-item>
<el-form-item label="商品原价">
<el-input v-model="form.originPrice"></el-input>
</el-form-item>
<el-form-item label="商品售价">
<el-input v-model="form.sellPrice"></el-input>
</el-form-item>
<el-form-item label="商品库存">
<el-input type="number" v-model.number="form.stock">
<template slot="append">件</template>
</el-input>
</el-form-item>
<el-row>
<el-col :span="18">
</el-col>
<el-col :span="12">
<el-form-item style="float: right;">
<el-button @click="dialogFormVisibleSave = false">取 消</el-button>
<el-button type="primary" @click="saveProduct(form)">保存</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button style="margin-top: 12px;" @click="pre">上一步</el-button>
<el-button style="margin-top: 12px;" @click="next">下一步</el-button>
</div>
</el-dialog>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2/dist/jquery.min.js"> </script>
<script src="static/js/base.js"></script>
<script src="static/js/axios.min.js"></script>
<script src="static/js/cookie_utils.js"></script>
<script>
var userId = getCookieValue("userId");
var token = getCookieValue("token");
new Vue({
el: '#app',
data: {
labelPosition: 'right',
active: 0,
illcodeInputShow: true,
show: true,
numRemark: 400,
url: "",
formLabelWidth: '120px',
dialogFormVisible: false,
imageUrl: "",
dialogFormVisibleSave: false,
dataForm: {
submienddte: "",
submistrdte: "",
isup: "",
categoryIds: [],
product_name: "",
categoryName: "",
categoryName1: "",
},
dataList: [{}, {}],
// 图片的定义的数组
fileList: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}],
obj:{
OSSAccessKeyId:"",
policy:"",
signature:"",
key:"",
host:"",
dir:"",
},
dialogVisible: false,
// 分类数据
categorydataList: [],
options: [{
value: 'zhinan',
label: '指南',
children: [{
value: 'shejiyuanze',
label: '设计原则',
children: [{
value: "",
lable: ""
}]
}]
}],
parentCategoryList: [],
subOptions1: [],
subOptions: [],
flag: false,
props: {
expandTrigger: 'hover',
//multiple:true, //这里设置为多选模式
value: "categoryId",
label: "categoryName", //展示的lable名字
children: "categories" //展示子级
},
defaultParams: {
value: 'value',
label: 'label',
children: this.subOptions
},
parenId: "",
sbusiorgMap: [{
sorgname: '1',
}],
category_id: 0,
submistrdte: "",
submienddte: "",
disabled: true,
page: {
pageIndex: 1,
pageSize: 10,
totalPage: 0,
},
form: {
pictureName: "",
sell_price: "",
url: "",
//图片数组
pics: [],
categoryIds: [],//分类id
},
// 公共数据
commonData: {
},
confirm: false,
// 商品分类查询的id
selectCategory_id1: 0,
selectCategory_id2: 0,
},
methods: {
handleChange(value) {
console.log("", value);
console.log("选中的值:", this.dataForm.categoryIds)
},
getCheckedNodes(leafOnly) {
console.log("选中节点数组", leafOnly)
},
passInput(value) {
// alert(value);
console.log(value, "value");
this.numRemark = 400 - value.length;
if (this.numRemark < 0) {
this.numRemark = 0;
}
if (value.length > 400) {
this.form.content = this.form.content.substring(0, 400);
this.$message({
message: "输入的说明不可以大于400字",
type: "error",
showClose: true,
});
return;
}
},
changeProductStatus(value) {
console.log("最新商品信息", value);
axios({
url: baseUrl + "product/updateProductStatus",
method: "post",
data: value,
}).then(res => {
console.log("商品状态返回数据", res.data);
if (res.data.code == 2) {
ELEMENT.Message.success(res.data.msg);
this.intDataList();
}
})
},
change(val) {
console.log(val, "Category2")
},
next() {
this.show = false;
if (this.active++ > 2) this.active = 0;
},
pre() {
this.show = true;
if (this.active-- < 0) this.active = 0;
},
changeSubCategory(val) {
// let label=this.$refs['cascaderAddr'].currentLabels;
// console.log(label);
this.dataForm.categoryName1 = null;
axios({
url: baseUrl + "index/subList",
method: "post",
headers: {
token: token
},
data: val,
}).then(res => {
this.subOptions = res.data.data;
console.log(this.subOptions);
console.log(res.data, "parent")
// ELEMENT.Message.success("更新商品信息成功");
})
},
queryParenList() {
axios({
url: baseUrl + "index/parentlist",
method: "post",
headers: {
token: token
},
data: userId,
}).then(res => {
this.parentCategoryList = res.data.data;
console.log(res.data, "parent")
// ELEMENT.Message.success("更新商品信息成功");
})
},
saveProduct(val) {
// 点击保存按钮---------- 图片的手动上传代码
// var vm = this;
// vm.$refs.doctypeCrfile.submit();
console.log("商品地址---------",this.form.url);
if (val.product_name == null || val.product_name.length == 0) {
ELEMENT.Message.error("商品名称不可以为空");
return;
}
if (val.categoryIds == null || val.categoryIds.length == 0) {
ELEMENT.Message.error("商品分类名称必须选择");
return;
}
if (val.originPrice.trim() == null) {
ELEMENT.Message.error("商品原价不可以为空");
return;
}
axios({
url: baseUrl + "product/add",
method: "post",
data: val,
}).then(res => {
// console.log(res.data.msg);
ELEMENT.Message.success(res.data.msg);
this.intDataList();
})
},
showCategorires() {
console.log("3333333333333333333")
var userId = getCookieValue("userId");
var url2 = baseUrl + "index/categorylist";
axios({
method: "post",
data: userId,
url: url2,
}).then(res => {
console.log("商品分类数据111111111111:", res.data.data);
this.categorydataList = res.data.data;
this.options = this.getTreeData(res.data.data);
this.options.children = res.data.data.categories;
this.options.value = res.data.data.categoryId;
this.options.label = res.data.data.categoryName;
console.log(this.options, "----------CategoriesdataList")
})
},
//递归判断列表,把最后的children设为undefined
getTreeData(data) {
for (var i = 0; i < data.length; i++) {
if (data[i].categories.length < 1) {
// children若为空数组,则将children设为undefined
data[i].categories = undefined;
} else {
// children若不为空数组,则继续 递归调用 本方法
this.getTreeData(data[i].categories);
}
}
return data;
},
// 商品保存
updateProduct(row) {
if (row.categoryIds == null || row.categoryIds.length == 0) {
ELEMENT.Message.error("商品分类不可以为空");
return;
}
axios({
url: baseUrl + "product/update",
method: "post",
data: row,
}).then(res => {
ELEMENT.Message.success("更新商品信息成功");
this.intDataList();
})
},
// 删除
deleteHandle(row, index) {
let params = {
product_id: row.product_id,
sku_id: row.sku_id,
};
this.$confirm(`确定进行删除操作?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
axios({
url: baseUrl + "product/delete",
method: 'post',
data: params
}).then((res) => {
console.log(res.data);
ELEMENT.Message.success(res.data.msg);
this.intDataList();
});
});
},
// // 公共代码段-复制、修改
// commenFn(row) {
// let copyObj = {
// product_name:row.product_name,
// category_name: row.category_name,
// content:row.content,
// isup:row.isup,
// soldNum:0,
// };
// this.commonData = copyObj;
// this.dataList.push(this.commonData);
// this.commonData = {};
// },
// 复制
copyHandle(row) {
// 功能代码
// this.commenFn(row);
this.dialogFormVisibleSave = true;
this.form = row;
this.illcodeInputShow = false;
ELEMENT.Message.success('复制成功');
},
addHandle(row) {
// 功能代码
// this.commenFn(row);
this.numRemark = 400;
this.form = {};
this.dialogFormVisibleSave = true;
},
// 修改功能
UpdateHandle(row) {
// 功能代码
this.category_id = row.category_id;
row.disabled = false;
this.confirm = false;
this.form = row;
this.dialogFormVisible = true;
},
dataBlur() {
if (this.dataForm.submistrdte > this.dataForm.submienddte) {
this.dataForm.submienddte = "";
ELEMENT.Message.error(
'开始日期不可以大于结束日期');
}
},
// 每页数
sizeChangeHandle(val) {
this.page.pageSize = val;
this.page.pageIndex = 1;
this.intDataList(2);
},
// 当前页
currentChangeHandle(val) {
this.page.pageIndex = val;
this.intDataList(2);
},
// 前往末页
toLastPage() {
let max = Math.ceil(this.tablePage.totalResult / this.tablePage.pageSize);
this.handleCurrentChange(max);
},
// 重置
resetForm() {
this.dataForm.category_id = "",
this.dataForm.product_name = "",
this.dataForm.submistrdte = "",
this.dataForm.submienddte = "",
this.dataForm.categoryName = "",
this.dataForm.categoryName1 = "",
this.dataForm.categoryIds = [];
this.dataForm.isup = "",
this.intDataList(1);
},
// 支持enter键查询
submit(val) {
// console.log(this.dataForm.branchname );
this.intDataList(val);
},
// 点击搜索按钮,请求数据
intDataList(val) {
// this.$store.dispatch("loading/CHANGE_LOADING", true);
let userId = getCookieValue("userId");
var params = {
pageNum: this.page.pageIndex,
pageSize: this.page.pageSize,
categoryIds: this.dataForm.categoryIds,
product_name: this.dataForm.product_name,
createTime: this.dataForm.submistrdte,
createTime1: this.dataForm.submienddte,
isup: this.dataForm.isup,
userId: userId
}
if (val === 1) {
//起始日期为空,截止日期赋值起始日期
if (this.submistrdte == '') {
this.submistrdte = this.submienddte
}
//截止日期为空,起始日期赋值截止日期
if (this.submienddte == '') {
this.submienddte = this.submistrdte
}
// 666
sessionStorage.setItem("datumTasks", JSON.stringify(params))
}
let token = getCookieValue("token");
var url5 = baseUrl + "product/list";
axios({
url: url5,
method: "post",
headers: {
token: token
},
data: params
}).then(res => {
this.dataList = [];
console.log(res.data, "dataList-------------");
this.dataList = res.data.list;
// this.page.pageNum=1
this.page.totalPage = res.data.total;
this.pageIndex = res.data.pageNum;
}).catch((err) => {
});
},
//oss实现文件上传
emitInput(val) {
this.$emit('input', val)
},
handleRemove(file, fileList) {
this.emitInput('');
},
ossPolicy(file){
// 上传前,获取服务器给的签名
axios({
url:"http://localhost:8080/oss/policy",
method:"get",
}).then(res=>{
console.log("come in ---------",res.data)
this.obj.OSSAccessKeyId = res.data.accessId;
this.obj.policy = res.data.policy;
this.obj.dir = res.data.dir;
this.obj.signature = res.data.signature;
//传到oss的服务器地址
this.obj.host = res.data.host;
this.obj.key =res.data.dir + "${filename}";
})
},
//文件上传成功时调用
handleUploadSuccess(res, file) {
console.log("上传成功...")
this.showFileList = true;
this.fileList.pop();
this.fileList.push({name: file.name, url: this.obj.host + '/' + this.obj.key.replace("${filename}",file.name) });
this.emitInput(this.fileList[0].url);
console.log("图片上传oss后的地址",this.fileList[0].url);
//赋值图片地址
this.form.url = this.fileList[0].url;
console.log(this.form.url);
},
},
created() {
this.intDataList();
this.showCategorires();
this.queryParenList();
}
})
</script>
</body>
</html>
1.后端Code
package com.qfedu.fmmall.controller;/*
**
*@author SmallMonkey
*@Date 2023/2/10 13:07
*
*
**/
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@CrossOrigin
@RequestMapping("/oss")
public class OssController {
@RequestMapping("/policy")
public Map<String,String> policy(){
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessId = "";
String accessKey = "yourkey";
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "oss-cn-hangzhou.aliyuncs.com";
// 填写Bucket名称,例如examplebucket。
String bucket = "fmmallrebuild";
// 填写Host地址,格式为https://bucketname.endpoint。
String host = "https://" + bucket + "." + endpoint;
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
// String callbackUrl = "https://192.168.0.0:8888";
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
//可以加上当前的日期
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dir = simpleDateFormat.format(new Date()) + "/";
//String objectName = dir + UUID.randomUUID().toString()+".png";
// 创建ossClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
Map<String, String> respMap = new LinkedHashMap<String, String>();
respMap.put("accessId", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
return respMap;
//服务进行上线之后,有一个外网可以访问的地址可以设置回调
/*
JSONObject jasonCallback = new JSONObject();
jasonCallback.put("callbackUrl", callbackUrl);
jasonCallback.put("callbackBody",
"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
respMap.put("callback", base64CallbackBody);
JSONObject ja1 = JSONObject.fromObject(respMap);*/
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return null;
}
}