From 8a1d2cd97377366c7321f874aea52b2b203bafdb Mon Sep 17 00:00:00 2001
From: Richie <280645618@qq.com>
Date: Sun, 2 Mar 2025 11:06:22 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8A=E4=BC=A0=E5=9B=BE?=
=?UTF-8?q?=E7=89=87=E7=9A=84bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 74 ++++----
.../cn/qihangerp/common/utils/FileUtils.java | 174 ++++++++++++++++++
sys-api/pom.xml | 5 +
.../sys/controller/ImageUploadController.java | 87 +++++++++
sys-api/src/main/resources/config.properties | 4 +
vue/src/components/ImageUpload/index.vue | 2 +-
vue/src/views/goods/create.vue | 18 +-
7 files changed, 314 insertions(+), 50 deletions(-)
create mode 100644 common/src/main/java/cn/qihangerp/common/utils/FileUtils.java
create mode 100644 sys-api/src/main/java/cn/qihangerp/sys/controller/ImageUploadController.java
create mode 100644 sys-api/src/main/resources/config.properties
diff --git a/README.md b/README.md
index 213d90aa..764fd236 100644
--- a/README.md
+++ b/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地址
}
}
```
diff --git a/common/src/main/java/cn/qihangerp/common/utils/FileUtils.java b/common/src/main/java/cn/qihangerp/common/utils/FileUtils.java
new file mode 100644
index 00000000..38344a52
--- /dev/null
+++ b/common/src/main/java/cn/qihangerp/common/utils/FileUtils.java
@@ -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);
+ }
+
+
+}
diff --git a/sys-api/pom.xml b/sys-api/pom.xml
index a54a4be6..9155d88b 100644
--- a/sys-api/pom.xml
+++ b/sys-api/pom.xml
@@ -61,6 +61,11 @@
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
+
+ com.qiniu
+ qiniu-java-sdk
+ [7.2.0, 7.2.99]
+
junit
junit
diff --git a/sys-api/src/main/java/cn/qihangerp/sys/controller/ImageUploadController.java b/sys-api/src/main/java/cn/qihangerp/sys/controller/ImageUploadController.java
new file mode 100644
index 00000000..e0396398
--- /dev/null
+++ b/sys-api/src/main/java/cn/qihangerp/sys/controller/ImageUploadController.java
@@ -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 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, "图片格式错误");
+ }
+ }
+}
diff --git a/sys-api/src/main/resources/config.properties b/sys-api/src/main/resources/config.properties
new file mode 100644
index 00000000..5fda5155
--- /dev/null
+++ b/sys-api/src/main/resources/config.properties
@@ -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
\ No newline at end of file
diff --git a/vue/src/components/ImageUpload/index.vue b/vue/src/components/ImageUpload/index.vue
index 1af622a3..1e24b2c1 100644
--- a/vue/src/components/ImageUpload/index.vue
+++ b/vue/src/components/ImageUpload/index.vue
@@ -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(),
},
diff --git a/vue/src/views/goods/create.vue b/vue/src/views/goods/create.vue
index cc451833..96411428 100644
--- a/vue/src/views/goods/create.vue
+++ b/vue/src/views/goods/create.vue
@@ -15,7 +15,7 @@
-
+
@@ -25,7 +25,7 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -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(),
},