修复上传图片的bug
This commit is contained in:
parent
5a1c4f66e9
commit
8a1d2cd973
74
README.md
74
README.md
|
|
@ -87,20 +87,7 @@ graph TD
|
|||
+ Jdk:17
|
||||
+ Nodejs:v16.20.0
|
||||
|
||||
#### 1.2 项目组件
|
||||
##### 后端核心组件
|
||||
+ SpringBoot:3.0.2
|
||||
+ spring-boot-starter-security
|
||||
+ SpringCloudAlibaba:2022.0.0.0
|
||||
+ Nacos
|
||||
+ SpringCloud Gateway
|
||||
+ spring-cloud-starter-loadbalancer
|
||||
|
||||
##### 前端框架及组件
|
||||
+ vue2
|
||||
+ element
|
||||
|
||||
#### 1.3、存储及中间件
|
||||
#### 1.2、存储及中间件
|
||||
|
||||
+ MySQL8
|
||||
+ Redis:7.x
|
||||
|
|
@ -108,34 +95,32 @@ graph TD
|
|||
+ Nacos:2.2.0(配置中心、注册中心)
|
||||
+ Sentinel(分布式流量治理组件)
|
||||
|
||||
`java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar`
|
||||
|
||||
#### 启动KRaft模式kafka
|
||||
+ 0 进入kafka解压目录
|
||||
+ 1 生成UUID`bin\windows\kafka-storage.bat random-uuid`
|
||||
+ 2 格式化`bin\windows\kafka-storage.bat format -t ujpyXZx-S9-jGlwxgORmow -c config\kraft\server.properties`
|
||||
+ 3 启动`bin\windows\kafka-server-start.bat config\kraft\server.properties`
|
||||
|
||||
|
||||
### 2、项目结构
|
||||
#### 2.1 core
|
||||
项目公共模块包括:
|
||||
#### 2.1 公共版本
|
||||
+`common`
|
||||
项目公共模块
|
||||
|
||||
+ `common`:公共类型
|
||||
+ `security`
|
||||
公共权限验证模块
|
||||
|
||||
+ `security`:公共权限验证模块
|
||||
|
||||
#### 2.2 gateway
|
||||
+ `goods`
|
||||
商品模块
|
||||
#### 2.2 微服务
|
||||
+ `gateway`
|
||||
网关项目,负责微服务接口转发,前端统一通过网关调用其他微服务接口;
|
||||
|
||||
采用`gateway`进行api分发,引入Sentinel进行流量治理。
|
||||
|
||||
#### 2.3 sys-api
|
||||
+ `sys-api`
|
||||
项目系统微服务,主要功能包括:
|
||||
|
||||
+ 用户
|
||||
+ 菜单
|
||||
|
||||
#### 2.4 oms-api
|
||||
+ `oms-api`
|
||||
oms主功能微服务,主要功能包括:
|
||||
|
||||
+ 队列消息处理(订单消息、退款消息)
|
||||
|
|
@ -143,7 +128,7 @@ oms主功能微服务,主要功能包括:
|
|||
+ 退款接口
|
||||
+ 店铺接口
|
||||
|
||||
#### 2.5 open-api
|
||||
+ `open-api`
|
||||
各开放平台微服务
|
||||
|
||||
+ 淘宝开放平台接口api
|
||||
|
|
@ -158,7 +143,7 @@ oms主功能微服务,主要功能包括:
|
|||
+ 拼多多开放平台接口api
|
||||
|
||||
|
||||
+微信视频号小店开放平台接口api
|
||||
+ 微信小店开放平台接口api
|
||||
|
||||
|
||||
|
||||
|
|
@ -169,10 +154,19 @@ oms主功能微服务,主要功能包括:
|
|||
#### 3.1、启动环境
|
||||
|
||||
1. 启动MySQL8
|
||||
|
||||
2. 启动Redis7
|
||||
3. 启动Sentinel1.8.7控制台
|
||||
4. 启动Nacos
|
||||
5. 启动Kafka
|
||||
|
||||
3. 启动Sentinel1.8.7控制台(可以不需要)
|
||||
`java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar`
|
||||
4. 启动Nacos(注册中心)
|
||||
|
||||
5. 启动Kafka(消息队列)
|
||||
`启动KRaft模式kafka`
|
||||
+ 0 进入kafka解压目录
|
||||
+ 1 生成UUID`bin\windows\kafka-storage.bat random-uuid`
|
||||
+ 2 格式化`bin\windows\kafka-storage.bat format -t ujpyXZx-S9-jGlwxgORmow -c config\kraft\server.properties`
|
||||
+ 3 启动`bin\windows\kafka-server-start.bat config\kraft\server.properties`
|
||||
|
||||
#### 3.2、导入数据库
|
||||
+ 创建数据库`qihang-oms`
|
||||
|
|
@ -180,16 +174,16 @@ oms主功能微服务,主要功能包括:
|
|||
|
||||
|
||||
#### 3.3、启动服务(项目)
|
||||
1. 启动开放平台微服务(open-api)
|
||||
2. 启动sys-api、oms-api微服务
|
||||
3. 启动微服务网关(api)
|
||||
1. 启动开放平台微服务(`open-api`)
|
||||
2. 启动`sys-api`、`oms-api`微服务
|
||||
3. 启动微服务网关(`gateway`)
|
||||
|
||||
#### 3.4、运行前端
|
||||
+ Nodejs版本:v16.20.0
|
||||
+ 进入`vue`文件夹
|
||||
+ 运行`npm install`
|
||||
+ 运行`npm run dev`
|
||||
+ 浏览网页`http://localhost`
|
||||
+ 浏览网页`http://localhost:88`
|
||||
|
||||
### 4、项目部署
|
||||
|
||||
|
|
@ -207,9 +201,9 @@ oms主功能微服务,主要功能包括:
|
|||
# 上传文件至远程服务器
|
||||
将打包生成在 `dist` 目录下的文件拷贝至 `/usr/share/nginx/html` 目录
|
||||
|
||||
# nginx.cofig 配置
|
||||
# nginx.cofig 配置(主要是配置接口转发)
|
||||
server {
|
||||
listen 80;
|
||||
listen 88;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
|
|
@ -217,7 +211,7 @@ server {
|
|||
}
|
||||
# 反向代理配置
|
||||
location /prod-api/ {
|
||||
proxy_pass http://127.0.0.1:8080/; # 替换成你的后端网关API地址
|
||||
proxy_pass http://127.0.0.1:8088/; # 替换成你的后端网关API地址
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
package cn.qihangerp.common.utils;
|
||||
|
||||
import com.alibaba.fastjson2.util.IOUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 文件处理工具类
|
||||
*
|
||||
* @author qihang
|
||||
*/
|
||||
public class FileUtils
|
||||
{
|
||||
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
|
||||
|
||||
/**
|
||||
* 输出指定文件的byte数组
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @param os 输出流
|
||||
* @return
|
||||
*/
|
||||
public static void writeBytes(String filePath, OutputStream os) throws IOException
|
||||
{
|
||||
FileInputStream fis = null;
|
||||
try
|
||||
{
|
||||
File file = new File(filePath);
|
||||
if (!file.exists())
|
||||
{
|
||||
throw new FileNotFoundException(filePath);
|
||||
}
|
||||
fis = new FileInputStream(file);
|
||||
byte[] b = new byte[1024];
|
||||
int length;
|
||||
while ((length = fis.read(b)) > 0)
|
||||
{
|
||||
os.write(b, 0, length);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtils.close(os);
|
||||
IOUtils.close(fis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件名重新编码
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @param fileName 文件名
|
||||
* @return 编码后的文件名
|
||||
*/
|
||||
public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
|
||||
{
|
||||
final String agent = request.getHeader("USER-AGENT");
|
||||
String filename = fileName;
|
||||
if (agent.contains("MSIE"))
|
||||
{
|
||||
// IE浏览器
|
||||
filename = URLEncoder.encode(filename, "utf-8");
|
||||
filename = filename.replace("+", " ");
|
||||
}
|
||||
else if (agent.contains("Firefox"))
|
||||
{
|
||||
// 火狐浏览器
|
||||
filename = new String(fileName.getBytes(), "ISO8859-1");
|
||||
}
|
||||
else if (agent.contains("Chrome"))
|
||||
{
|
||||
// google浏览器
|
||||
filename = URLEncoder.encode(filename, "utf-8");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其它浏览器
|
||||
filename = URLEncoder.encode(filename, "utf-8");
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件名重新编码
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @param realFileName 真实文件名
|
||||
*/
|
||||
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
|
||||
{
|
||||
String percentEncodedFileName = percentEncode(realFileName);
|
||||
|
||||
StringBuilder contentDispositionValue = new StringBuilder();
|
||||
contentDispositionValue.append("attachment; filename=")
|
||||
.append(percentEncodedFileName)
|
||||
.append(";")
|
||||
.append("filename*=")
|
||||
.append("utf-8''")
|
||||
.append(percentEncodedFileName);
|
||||
|
||||
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
|
||||
response.setHeader("Content-disposition", contentDispositionValue.toString());
|
||||
response.setHeader("download-filename", percentEncodedFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 百分号编码工具方法
|
||||
*
|
||||
* @param s 需要百分号编码的字符串
|
||||
* @return 百分号编码后的字符串
|
||||
*/
|
||||
public static String percentEncode(String s) throws UnsupportedEncodingException
|
||||
{
|
||||
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
|
||||
return encode.replaceAll("\\+", "%20");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图像后缀
|
||||
*
|
||||
* @param photoByte 图像数据
|
||||
* @return 后缀名
|
||||
*/
|
||||
public static String getFileExtendName(byte[] photoByte)
|
||||
{
|
||||
String strFileExtendName = "jpg";
|
||||
if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
|
||||
&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
|
||||
{
|
||||
strFileExtendName = "gif";
|
||||
}
|
||||
else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
|
||||
{
|
||||
strFileExtendName = "jpg";
|
||||
}
|
||||
else if ((photoByte[0] == 66) && (photoByte[1] == 77))
|
||||
{
|
||||
strFileExtendName = "bmp";
|
||||
}
|
||||
else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
|
||||
{
|
||||
strFileExtendName = "png";
|
||||
}
|
||||
return strFileExtendName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名称 /profile/upload/2022/04/16/zhijian.png -- zhijian.png
|
||||
*
|
||||
* @param fileName 路径名称
|
||||
* @return 没有文件路径的名称
|
||||
*/
|
||||
public static String getName(String fileName)
|
||||
{
|
||||
if (fileName == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int lastUnixPos = fileName.lastIndexOf('/');
|
||||
int lastWindowsPos = fileName.lastIndexOf('\\');
|
||||
int index = Math.max(lastUnixPos, lastWindowsPos);
|
||||
return fileName.substring(index + 1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -61,6 +61,11 @@
|
|||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>[7.2.0, 7.2.99]</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
package cn.qihangerp.sys.controller;
|
||||
|
||||
|
||||
import cn.qihangerp.common.common.AjaxResult;
|
||||
import cn.qihangerp.common.utils.FileUtils;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.qiniu.common.QiniuException;
|
||||
import com.qiniu.common.Zone;
|
||||
import com.qiniu.http.Response;
|
||||
import com.qiniu.storage.Configuration;
|
||||
import com.qiniu.storage.UploadManager;
|
||||
import com.qiniu.util.Auth;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Properties;
|
||||
|
||||
@RestController
|
||||
public class ImageUploadController {
|
||||
|
||||
@RequestMapping("/images/upload")
|
||||
public AjaxResult uploadImage(MultipartFile file) throws IOException {
|
||||
if (file.isEmpty()) return AjaxResult.error(400, "请选择图片");
|
||||
String fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1, file.getOriginalFilename().length());
|
||||
ArrayList<String> a = new ArrayList<>();
|
||||
a.add("gif");
|
||||
a.add("jpg");
|
||||
a.add("jpeg");
|
||||
a.add("png");
|
||||
if (a.contains(fileSuffix) == false) return AjaxResult.error(400, "不支持的格式");
|
||||
String fileName = file.getOriginalFilename();
|
||||
//appid配置
|
||||
Properties properties = PropertiesLoaderUtils.loadAllProperties("config.properties");
|
||||
String qiniu_img_domain = properties.getProperty("qiniu_img_domain");
|
||||
String qiniu_access_key = properties.getProperty("qiniu_access_key");
|
||||
String qiniu_secret_key = properties.getProperty("qiniu_secret_key");
|
||||
String qiniu_bucket = properties.getProperty("qiniu_bucket");
|
||||
if(StringUtils.isBlank(qiniu_img_domain)) return AjaxResult.error("请配置七牛云参数");
|
||||
if(StringUtils.isBlank(qiniu_access_key)) return AjaxResult.error("请配置七牛云参数");
|
||||
if(StringUtils.isBlank(qiniu_secret_key)) return AjaxResult.error("请配置七牛云参数");
|
||||
if(StringUtils.isBlank(qiniu_bucket)) return AjaxResult.error("请配置七牛云参数");
|
||||
|
||||
Auth auth = Auth.create(qiniu_access_key, qiniu_secret_key);
|
||||
|
||||
String upToken = auth.uploadToken(qiniu_bucket);//上传的凭据
|
||||
|
||||
//构造一个带指定Zone对象的配置类
|
||||
Configuration cfg = new Configuration(Zone.zone0());
|
||||
|
||||
UploadManager uploadManager = new UploadManager(cfg);
|
||||
//默认不指定key的情况下,以文件内容的hash值作为文件名
|
||||
String key = null;
|
||||
// Response response = uploadManager.put(file, key, upToken);
|
||||
try {
|
||||
Response response = uploadManager.put(file.getInputStream(), key, upToken, null, null);
|
||||
//解析上传成功的结果
|
||||
var res = JSONObject.parseObject(response.bodyString());
|
||||
|
||||
String url = qiniu_img_domain + res.getString("key");
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("url", url);
|
||||
ajax.put("fileName", fileName);
|
||||
ajax.put("newFileName", FileUtils.getName(fileName));
|
||||
ajax.put("originalFilename", file.getOriginalFilename());
|
||||
return ajax;
|
||||
} catch (QiniuException ex) {
|
||||
Response r = ex.response;
|
||||
|
||||
System.err.println(r.toString());
|
||||
try {
|
||||
System.err.println(r.bodyString());
|
||||
return AjaxResult.error(500, r.bodyString());
|
||||
} catch (QiniuException ex2) {
|
||||
//ignore
|
||||
return AjaxResult.error(500, "图片上传错误");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error(400, "图片格式错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
qiniu_img_domain=http://img.huayiyungou.com/
|
||||
qiniu_access_key=
|
||||
qiniu_secret_key=utM-_huV78h7GaWWsxKDl97P5EFK5jmb0ba-3HIG
|
||||
qiniu_bucket=ttxs100-cn-files
|
||||
|
|
@ -77,7 +77,7 @@ export default {
|
|||
dialogVisible: false,
|
||||
hideUpload: false,
|
||||
baseUrl: '',// process.env.VUE_APP_BASE_API,
|
||||
uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
|
||||
uploadImgUrl: process.env.VUE_APP_BASE_API + "/api/sys-api/images/upload", // 上传的图片服务器地址
|
||||
headers: {
|
||||
Authorization: "Bearer " + getToken(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入商品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品图片1" prop="image">
|
||||
<el-form-item label="商品图片" prop="image">
|
||||
<image-upload v-model="form.image" :limit="1"/>
|
||||
<el-input v-model="form.image" placeholder="请输入商品图片" />
|
||||
</el-form-item>
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
<el-form-item label="外部ERP商品ID" prop="outerErpGoodsId" >
|
||||
<el-input v-model="form.outerErpGoodsId" placeholder="请输入外部ERP商品ID" style="width:220px"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="预计采购价格" prop="purPrice">
|
||||
<el-form-item label="预计采购价" prop="purPrice">
|
||||
<el-input type="number" v-model.number="form.purPrice" placeholder="请输入预计采购价格" style="width:220px"/>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="建议批发价" prop="wholePrice">
|
||||
|
|
@ -34,12 +34,12 @@
|
|||
<el-form-item label="建议零售价" prop="retailPrice">
|
||||
<el-input type="number" v-model.number="form.retailPrice" placeholder="请输入建议零售价" style="width:220px"/>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="单位名称" prop="unitName">
|
||||
<el-input v-model="form.unitName" placeholder="请输入单位名称" style="width:220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="条码" prop="barCode">
|
||||
<el-input v-model="form.barCode" placeholder="请输入条码" style="width:220px"/>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="单位名称" prop="unitName">-->
|
||||
<!-- <el-input v-model="form.unitName" placeholder="请输入单位名称" style="width:220px" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- <el-form-item label="条码" prop="barCode">-->
|
||||
<!-- <el-input v-model="form.barCode" placeholder="请输入条码" style="width:220px"/>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
|
|
@ -239,7 +239,7 @@ export default {
|
|||
components: { Treeselect },
|
||||
data() {
|
||||
return {
|
||||
uploadImgUrl: process.env.VUE_APP_BASE_API + "/api/oms-api/images/upload",
|
||||
uploadImgUrl: process.env.VUE_APP_BASE_API + "/api/sys-api/images/upload",
|
||||
headers: {
|
||||
Authorization: "Bearer " + getToken(),
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue