修复上传图片的bug

This commit is contained in:
Richie 2025-03-02 11:06:22 +08:00
parent 5a1c4f66e9
commit 8a1d2cd973
7 changed files with 314 additions and 50 deletions

View File

@ -87,20 +87,7 @@ graph TD
+ Jdk17
+ Nodejsv16.20.0
#### 1.2 项目组件
##### 后端核心组件
+ SpringBoot3.0.2
+ spring-boot-starter-security
+ SpringCloudAlibaba2022.0.0.0
+ Nacos
+ SpringCloud Gateway
+ spring-cloud-starter-loadbalancer
##### 前端框架及组件
+ vue2
+ element
#### 1.3、存储及中间件
#### 1.2、存储及中间件
+ MySQL8
+ Redis7.x
@ -108,34 +95,32 @@ graph TD
+ Nacos2.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地址
}
}
```

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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, "图片格式错误");
}
}
}

View File

@ -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

View File

@ -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(),
},

View File

@ -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(),
},