Browse Source

前端图片上传改为经过后端

zoujiajian 1 tuần trước cách đây
mục cha
commit
2321ae52c4

+ 0 - 5
netflix-dao/src/main/java/com/cyksj/model/request/TaskFollowReq.java

@@ -32,11 +32,6 @@ public class TaskFollowReq {
 	 */
 	private String platNickname;
 
-	/**
-	 * 关注平台截图
-	 */
-	private String img;
-
 	private Long userId;
 
 	/**

+ 3 - 2
netflix-service/src/main/java/com/cyksj/service/market/MarketFrontService.java

@@ -8,6 +8,7 @@ import com.cyksj.model.request.LuckyDrawReq;
 import com.cyksj.model.request.TaskFollowReq;
 import com.cyksj.model.views.LuckyDrawView;
 import com.cyksj.model.views.TaskFrontView;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.Date;
 import java.util.List;
@@ -25,9 +26,9 @@ public interface MarketFrontService {
 
 	void receivedTaskPoints(Long userId, Long taskId);
 
-	void toCompleteFollowTask(TaskFollowReq taskFollowReq);
+	void toCompleteFollowTask(TaskFollowReq taskFollowReq, MultipartFile file);
 
-	void updateFailFollowTask(TaskFollowReq taskFollowReq);
+	void updateFailFollowTask(TaskFollowReq taskFollowReq, MultipartFile file);
 
 	Integer getSpecificChanceNum(Long userId);
 

+ 95 - 0
netflix-service/src/main/java/com/cyksj/service/market/ThirdPartyFileUploadService.java

@@ -0,0 +1,95 @@
+package com.cyksj.service.market;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+/** 服务端代理上传文件到第三方存储。 */
+@Service
+public class ThirdPartyFileUploadService {
+	private static final String UPLOAD_URL = "https://upload.liuliangbang.vip/internal/27d3e096b0be2ceb73fb5dc1094db509b6febcda61bce99d/upload";
+	private static final long MAX_IMAGE_SIZE = 5L * 1024 * 1024;
+
+	public String upload(MultipartFile file) {
+		if (file == null || file.isEmpty()) {
+			throw BusinessRuntimeException.getInstance("上传文件不能为空");
+		}
+		String originalName = file.getOriginalFilename();
+		String extension = "";
+		if (originalName != null) {
+			int dot = originalName.lastIndexOf('.');
+			if (dot >= 0 && dot < originalName.length() - 1) {
+				String candidate = originalName.substring(dot + 1).toLowerCase(java.util.Locale.ROOT);
+				if (candidate.matches("[a-z0-9]{1,10}")) {
+					extension = "." + candidate;
+				}
+			}
+		}
+		return upload(file, "upload-" + UUID.randomUUID() + extension);
+	}
+
+	public String uploadImage(MultipartFile file) {
+		String extension = validateImage(file);
+		return upload(file, "upload-" + UUID.randomUUID() + "." + extension);
+	}
+
+	private String upload(MultipartFile file, String filename) {
+		try (HttpResponse response = HttpRequest.post(UPLOAD_URL)
+				.form("file", file.getBytes(), filename)
+				.setConnectionTimeout(5000).setReadTimeout(15000).execute()) {
+			if (!response.isOk()) {
+				throw BusinessRuntimeException.getInstance("文件上传失败,请稍后重试");
+			}
+			JSONObject result = JSONUtil.parseObj(response.body());
+			String url = JSONUtil.parseObj(result.getJSONObject("value").getJSONArray("saved").get(0))
+					.getJSONObject("info").getStr("cdnUrl");
+			if (url == null || url.isBlank() || !"https".equalsIgnoreCase(URI.create(url).getScheme())) {
+				throw BusinessRuntimeException.getInstance("文件上传失败,请稍后重试");
+			}
+			return url;
+		} catch (BusinessRuntimeException e) {
+			throw e;
+		} catch (Exception e) {
+			throw BusinessRuntimeException.getInstance(e, "文件上传失败,请稍后重试");
+		}
+	}
+
+	private String validateImage(MultipartFile file) {
+		if (file == null || file.isEmpty()) {
+			throw BusinessRuntimeException.getInstance("请上传图片");
+		}
+		if (file.getSize() > MAX_IMAGE_SIZE) {
+			throw BusinessRuntimeException.getInstance("图片不能超过5MB");
+		}
+		byte[] header = new byte[12];
+		try (InputStream stream = file.getInputStream()) {
+			int length = stream.read(header);
+			if (length >= 8 && (header[0] & 0xff) == 0x89 && header[1] == 'P'
+					&& header[2] == 'N' && header[3] == 'G' && header[4] == 13
+					&& header[5] == 10 && header[6] == 26 && header[7] == 10) {
+				return "png";
+			}
+			if (length >= 3 && (header[0] & 0xff) == 0xff && (header[1] & 0xff) == 0xd8
+					&& (header[2] & 0xff) == 0xff) {
+				return "jpg";
+			}
+			if (length >= 12 && new String(header, 0, 4, StandardCharsets.US_ASCII).equals("RIFF")
+					&& new String(header, 8, 4, StandardCharsets.US_ASCII).equals("WEBP")) {
+				return "webp";
+			}
+		} catch (IOException e) {
+			throw BusinessRuntimeException.getInstance(e, "读取图片失败");
+		}
+		throw BusinessRuntimeException.getInstance("仅支持 PNG、JPG、WEBP 图片");
+	}
+}

+ 31 - 9
netflix-service/src/main/java/com/cyksj/service/market/impl/MarketFrontServiceImpl.java

@@ -35,6 +35,7 @@ import com.cyksj.service.coin.UserGalaxyCoinService;
 import com.cyksj.service.exchange.ExchangeCodeService;
 import com.cyksj.service.mange.coupon.CouponCommonService;
 import com.cyksj.service.market.MarketFrontService;
+import com.cyksj.service.market.ThirdPartyFileUploadService;
 import com.cyksj.service.relation.GroupRelationFrontService;
 import com.cyksj.service.sys.SysConfigService;
 import com.ejlchina.searcher.BeanSearcher;
@@ -46,6 +47,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
@@ -105,6 +107,8 @@ public class MarketFrontServiceImpl implements MarketFrontService {
 
 	private final UserGalaxyCoinService userGalaxyCoinService;
 
+	private final ThirdPartyFileUploadService thirdPartyFileUploadService;
+
 	@Override
 	public SignUser signByUserId(Long userId) {
 		//第一次签到
@@ -306,20 +310,29 @@ public class MarketFrontServiceImpl implements MarketFrontService {
 
 	@Override
 	@Transactional(rollbackFor = Throwable.class)
-	public void toCompleteFollowTask(TaskFollowReq taskFollowReq) {
+	public void toCompleteFollowTask(TaskFollowReq taskFollowReq, MultipartFile file) {
 		Long taskId = taskFollowReq.getTaskId();
 		Long userId = taskFollowReq.getUserId();
 		TaskFrontView data = beanSearcher.searchFirst(TaskFrontView.class, MapUtils.builder()
 				.field(TaskFrontView::getId, taskId).build());
+		if (data == null) {
+			throw BusinessRuntimeException.getInstance("该任务不存在");
+		}
 		//非审核
-		if (!data.getIsVerify()) {
-			throw BusinessRuntimeException.getInstance("");
+		if (!Boolean.TRUE.equals(data.getIsVerify())) {
+			throw BusinessRuntimeException.getInstance("该任务不需要提交审核");
+		}
+		if (taskFollowPlatVerifyMapper.selectCount(Wrappers.lambdaQuery(TaskFollowPlatVerify.class)
+				.eq(TaskFollowPlatVerify::getTaskId, taskId)
+				.eq(TaskFollowPlatVerify::getUserId, userId)) > 0) {
+			throw BusinessRuntimeException.getInstance("请勿重复提交关注任务");
 		}
+		String imgUrl = file == null || file.isEmpty() ? null : thirdPartyFileUploadService.uploadImage(file);
 		TaskFollowPlatVerify taskFollowPlatVerify = new TaskFollowPlatVerify();
 		taskFollowPlatVerify.setTaskId(taskId);
 		taskFollowPlatVerify.setUserId(userId);
 		taskFollowPlatVerify.setPlatNickname(taskFollowReq.getPlatNickname());
-		taskFollowPlatVerify.setImg(taskFollowReq.getImg());
+		taskFollowPlatVerify.setImg(imgUrl);
 		taskFollowPlatVerify.setLink(taskFollowReq.getLink());
 		try {
 			taskFollowPlatVerifyMapper.insert(taskFollowPlatVerify);
@@ -330,12 +343,13 @@ public class MarketFrontServiceImpl implements MarketFrontService {
 			record.setStatus(TaskUserCompleteRecord.Status.verifying);
 			taskUserCompleteRecordMapper.insert(record);
 		} catch (DuplicateKeyException e) {
-
+			throw BusinessRuntimeException.getInstance("请勿重复提交关注任务");
 		}
 	}
 
 	@Override
-	public void updateFailFollowTask(TaskFollowReq taskFollowReq) {
+	@Transactional(rollbackFor = Throwable.class)
+	public void updateFailFollowTask(TaskFollowReq taskFollowReq, MultipartFile file) {
 		if (taskFollowReq.getId() == null || taskFollowReq.getId() < 1) {
 			throw BusinessRuntimeException.getInstance("");
 		}
@@ -343,12 +357,20 @@ public class MarketFrontServiceImpl implements MarketFrontService {
 		if (dbTaskFollowPlatVerify == null) {
 			throw BusinessRuntimeException.getInstance("该提交记录不存在");
 		}
-		if (dbTaskFollowPlatVerify.getStatus() == TaskFollowPlatVerify.Status.success) {
-			throw BusinessRuntimeException.getInstance("您提交的记录已审核成功");
+		if (!taskFollowReq.getUserId().equals(dbTaskFollowPlatVerify.getUserId())) {
+			throw BusinessRuntimeException.getInstance("该提交记录不存在");
+		}
+		if (dbTaskFollowPlatVerify.getStatus() != TaskFollowPlatVerify.Status.fail) {
+			throw BusinessRuntimeException.getInstance("仅审核失败的记录可以重新提交");
+		}
+		if (!taskFollowReq.getTaskId().equals(dbTaskFollowPlatVerify.getTaskId())) {
+			throw BusinessRuntimeException.getInstance("任务与提交记录不匹配");
 		}
 		dbTaskFollowPlatVerify.setPlatNickname(taskFollowReq.getPlatNickname());
 		dbTaskFollowPlatVerify.setStatus(TaskFollowPlatVerify.Status.verifying);
-		dbTaskFollowPlatVerify.setImg(taskFollowReq.getImg());
+		if (file != null && !file.isEmpty()) {
+			dbTaskFollowPlatVerify.setImg(thirdPartyFileUploadService.uploadImage(file));
+		}
 		taskFollowPlatVerifyMapper.updateById(dbTaskFollowPlatVerify);
 
 		TaskUserCompleteRecord record = taskUserCompleteRecordMapper.selectOne(Wrappers.lambdaQuery(TaskUserCompleteRecord.class)

+ 2 - 1
netflix-service/src/main/java/com/cyksj/service/order/OrderCommentFrontService.java

@@ -1,6 +1,7 @@
 package com.cyksj.service.order;
 
 import com.cyksj.model.request.OrderCommentReq;
+import org.springframework.web.multipart.MultipartFile;
 
 /*
  *项目名: netflix
@@ -9,5 +10,5 @@ import com.cyksj.model.request.OrderCommentReq;
  *创建时间:2023/7/28 13:46
  */
 public interface OrderCommentFrontService {
-	void saveComment(OrderCommentReq request);
+	void saveComment(OrderCommentReq request, MultipartFile[] files);
 }

+ 22 - 2
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderCommentFrontServiceImpl.java

@@ -3,6 +3,7 @@ package com.cyksj.service.order.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.constant.Constant;
 import com.cyksj.common.exception.BusinessRuntimeException;
@@ -15,6 +16,7 @@ import com.cyksj.model.manage.views.OrderDonView;
 import com.cyksj.model.request.OrderCommentReq;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.order.OrderCommentFrontService;
+import com.cyksj.service.market.ThirdPartyFileUploadService;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.param.Operator;
 import com.ejlchina.searcher.util.MapUtils;
@@ -23,6 +25,10 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import org.springframework.web.multipart.MultipartFile;
 
 /*
  *项目名: netflix
@@ -44,8 +50,21 @@ public class OrderCommentFrontServiceImpl implements OrderCommentFrontService {
 
 	private final RedisService redisService;
 
+	private final ThirdPartyFileUploadService thirdPartyFileUploadService;
+
 	@Override
-	public void saveComment(OrderCommentReq request) {
+	public void saveComment(OrderCommentReq request, MultipartFile[] files) {
+		if (files != null && files.length > 9) {
+			throw BusinessRuntimeException.getInstance("最多上传9张图片");
+		}
+		if (files != null && files.length > 0) {
+			String picture = JSONUtil.toJsonStr(Arrays.stream(files)
+					.filter(file -> file != null && !file.isEmpty())
+					.map(thirdPartyFileUploadService::uploadImage)
+					.map(url -> Collections.singletonMap("url", url))
+					.collect(Collectors.toList()));
+			request.setPicture(picture);
+		}
 		Long orderId = request.getOrderId();
 		Long userId = request.getUserId();
 		List<Long> userIds = request.getUserIds();
@@ -66,7 +85,8 @@ public class OrderCommentFrontServiceImpl implements OrderCommentFrontService {
 		if (request.getContent().length() > 1000) {
 			throw BusinessRuntimeException.getInstance("评论内容过多");
 		}
-		if (StrUtil.isNotBlank(request.getPicture()) && request.getPicture().length() > 2000) {
+		// 最多9张图片以 [{"url":"CDN地址"}] JSON数组保存。
+		if (StrUtil.isNotBlank(request.getPicture()) && request.getPicture().length() > 10000) {
 			throw BusinessRuntimeException.getInstance("图片过多");
 		}
 		if (Constant.noOrderStatus.contains(orderDonView.getStatus().name())) {

+ 11 - 6
netflix-web/src/main/java/com/cyksj/web/controller/market/TaskFrontController.java

@@ -25,7 +25,9 @@ import com.ejlchina.searcher.util.StringUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -175,12 +177,13 @@ public class TaskFrontController {
 	/**
 	 * 去完成关注任务
 	 */
-	@PostMapping("/post/follow")
+	@PostMapping(value = "/post/follow", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
 	@NoSubmit
-	public Result<String> toCompleteFollowTask(@RequestBody @Validated TaskFollowReq taskFollowReq) {
+	public Result<String> toCompleteFollowTask(@ModelAttribute @Validated TaskFollowReq taskFollowReq,
+			@RequestParam(value = "file", required = false) MultipartFile file) {
 		Long userId = StpUserUtil.getLoginIdAsLong();
 		taskFollowReq.setUserId(userId);
-		marketFrontService.toCompleteFollowTask(taskFollowReq);
+		marketFrontService.toCompleteFollowTask(taskFollowReq, file);
 		return GatewayResponse.SUCCESS.newBuilder().toResult("提交成功");
 	}
 
@@ -198,11 +201,13 @@ public class TaskFrontController {
 	/**
 	 * 关注任务审核失败,重新提交
 	 */
-	@PutMapping("/put/follow")
-	public Result<String> updateFailFollowTask(@RequestBody @Validated TaskFollowReq taskFollowReq) {
+	@PutMapping(value = "/put/follow", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+	@NoSubmit
+	public Result<String> updateFailFollowTask(@ModelAttribute @Validated TaskFollowReq taskFollowReq,
+			@RequestParam(value = "file", required = false) MultipartFile file) {
 		Long userId = StpUserUtil.getLoginIdAsLong();
 		taskFollowReq.setUserId(userId);
-		marketFrontService.updateFailFollowTask(taskFollowReq);
+		marketFrontService.updateFailFollowTask(taskFollowReq, file);
 		return GatewayResponse.SUCCESS.newBuilder().toResult("提交成功");
 	}
 }

+ 6 - 3
netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderCommentFrontController.java

@@ -13,8 +13,10 @@ import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
 
@@ -38,13 +40,14 @@ public class OrderCommentFrontController {
 	/**
 	 * 新增评价
 	 */
-	@PostMapping("/post")
+	@PostMapping(value = "/post", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
 	@NoSubmit
-	public Result<String> post(@RequestBody @Validated OrderCommentReq request) {
+	public Result<String> post(@ModelAttribute @Validated OrderCommentReq request,
+			@RequestParam(value = "files", required = false) MultipartFile[] files) {
 		long userId = StpUserUtil.getLoginIdAsLong();
 		request.setUserId(userId);
 		request.setUserIds(userService.getRelationUserIdList(userId, null));
-		orderCommentFrontService.saveComment(request);
+		orderCommentFrontService.saveComment(request, files);
 		return GatewayResponse.SUCCESS.newBuilder().toResult();
 	}