diff --git a/src/main/java/com/cultural/heritage/controller/clothes/ClothesController.java b/src/main/java/com/cultural/heritage/controller/clothes/ClothesController.java index 51d4e2e..1c5405f 100644 --- a/src/main/java/com/cultural/heritage/controller/clothes/ClothesController.java +++ b/src/main/java/com/cultural/heritage/controller/clothes/ClothesController.java @@ -1,6 +1,8 @@ package com.cultural.heritage.controller.clothes; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; @@ -17,24 +19,31 @@ import com.cultural.heritage.model.dto.CommonRequest; import com.cultural.heritage.model.dto.clothes.ClothesAddRequest; import com.cultural.heritage.model.dto.clothes.ClothesQueryRequest; import com.cultural.heritage.model.dto.clothes.ClothesUpdateRequest; +import com.cultural.heritage.model.dto.facelift.ClothesUrlObj; +import com.cultural.heritage.model.dto.facelift.GarmentObj; +import com.cultural.heritage.model.dto.facelift.LogoUrlObj; +import com.cultural.heritage.model.dto.facelift.ModelObj; import com.cultural.heritage.model.entity.Clothes; import com.cultural.heritage.model.vo.clothes.ClothesLabelVO; import com.cultural.heritage.model.vo.clothes.ClothesVO; import com.cultural.heritage.service.clothes.ClothesService; import com.cultural.heritage.service.common.CommonService; import com.cultural.heritage.utils.DecoderUtils; +import com.cultural.heritage.utils.Sign; +import com.google.gson.Gson; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; @RestController @@ -292,6 +301,47 @@ public class ClothesController { + /** + * 小程序端用户调用换装api + * @param modelURL 模特Url + * @param clothesURL 服装URL + * @return + */ + @PostMapping("/facelift") + @Operation(summary = "小程序端用户调用换装api", description = "参数:服装id,权限:管理员(admin, boss),方法名:updateClothesShelvesStatus") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse invokeChangeClothesApi(@RequestParam String modelURL, @RequestParam String clothesURL) { + if (StringUtils.isBlank(modelURL) || StringUtils.isBlank(clothesURL)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空"); + } + Map param = new HashMap<>(); + param.put("req_key", "dressing_diffusion"); + ModelObj modelObj = ModelObj.builder().id("1").url(modelURL).build(); + param.put("model", modelObj); + ClothesUrlObj clothesUrlObj = ClothesUrlObj.builder().url(clothesURL).build(); + List clothesUrlObjList = new ArrayList<>(); + clothesUrlObjList.add(clothesUrlObj); + GarmentObj garmentObj = GarmentObj.builder().id("1").data(clothesUrlObjList).build(); + param.put("garment", garmentObj); + param.put("return_url", true); + LogoUrlObj logoUrlObj = LogoUrlObj.builder().add_logo(false).position(0).language(0).logo_text_content("这里是明水印内容").build(); + param.put("logo_url", logoUrlObj); + // 使用 Gson 将 Map 转换为 byte[] + Gson gson = new Gson(); + byte[] body = gson.toJson(param).getBytes(StandardCharsets.UTF_8); + String responseBody = null; + try { + responseBody = Sign.invokeChangeClothesApi(body); + } catch (Exception e) { + e.printStackTrace(); + } + // 使用 fastjson 将 responseBody 字符串解析为 JSONObject + JSONObject jsonObject = JSONObject.parseObject(responseBody); + + // 从 JSON 中获取 "data" 对象,然后获取 "image_urls" 数组 + JSONArray imageUrls = jsonObject.getJSONObject("data").getJSONArray("image_urls"); + return ResultUtils.success(imageUrls.getString(0)); + } diff --git a/src/main/java/com/cultural/heritage/model/dto/facelift/ClothesUrlObj.java b/src/main/java/com/cultural/heritage/model/dto/facelift/ClothesUrlObj.java new file mode 100644 index 0000000..25c1b87 --- /dev/null +++ b/src/main/java/com/cultural/heritage/model/dto/facelift/ClothesUrlObj.java @@ -0,0 +1,24 @@ +package com.cultural.heritage.model.dto.facelift; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ClothesUrlObj implements Serializable { + + /** + * 服装url + */ + private String url; + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/cultural/heritage/model/dto/facelift/GarmentObj.java b/src/main/java/com/cultural/heritage/model/dto/facelift/GarmentObj.java new file mode 100644 index 0000000..1d188f4 --- /dev/null +++ b/src/main/java/com/cultural/heritage/model/dto/facelift/GarmentObj.java @@ -0,0 +1,31 @@ +package com.cultural.heritage.model.dto.facelift; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class GarmentObj implements Serializable { + + /** + * 模特配置 + */ + private String id; + + /** + * 服装URL列表 + */ + private List data; + + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/cultural/heritage/model/dto/facelift/LogoUrlObj.java b/src/main/java/com/cultural/heritage/model/dto/facelift/LogoUrlObj.java new file mode 100644 index 0000000..ecbd194 --- /dev/null +++ b/src/main/java/com/cultural/heritage/model/dto/facelift/LogoUrlObj.java @@ -0,0 +1,39 @@ +package com.cultural.heritage.model.dto.facelift; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class LogoUrlObj implements Serializable { + + /** + * 是否添加水印 + */ + private Boolean add_logo; + + /** + * 水印位置 + */ + private Integer position; + + /** + * 水印的语言 + */ + private Integer language; + + /** + * 水印的内容 + */ + private String logo_text_content; + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/cultural/heritage/model/dto/facelift/ModelObj.java b/src/main/java/com/cultural/heritage/model/dto/facelift/ModelObj.java new file mode 100644 index 0000000..9ecd697 --- /dev/null +++ b/src/main/java/com/cultural/heritage/model/dto/facelift/ModelObj.java @@ -0,0 +1,31 @@ +package com.cultural.heritage.model.dto.facelift; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ModelObj implements Serializable { + + /** + * 模特配置 + */ + private String id; + + /** + * 模特URL + */ + private String url; + + + @Serial + private static final long serialVersionUID = 4460640673274461809L; + +} diff --git a/src/main/java/com/cultural/heritage/utils/Sign.java b/src/main/java/com/cultural/heritage/utils/Sign.java new file mode 100644 index 0000000..815919a --- /dev/null +++ b/src/main/java/com/cultural/heritage/utils/Sign.java @@ -0,0 +1,258 @@ +package com.cultural.heritage.utils; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Copyright (year) Beijing Volcano Engine Technology Ltd. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +public class Sign { + + private static final BitSet URLENCODER = new BitSet(256); + + private static final String CONST_ENCODE = "0123456789ABCDEF"; + public static final Charset UTF_8 = StandardCharsets.UTF_8; + + private final String region; + private final String service; + private final String schema; + private final String host; + private final String path; + private final String ak; + private final String sk; + + static { + int i; + for (i = 97; i <= 122; ++i) { + URLENCODER.set(i); + } + + for (i = 65; i <= 90; ++i) { + URLENCODER.set(i); + } + + for (i = 48; i <= 57; ++i) { + URLENCODER.set(i); + } + URLENCODER.set('-'); + URLENCODER.set('_'); + URLENCODER.set('.'); + URLENCODER.set('~'); + } + + public Sign(String region, String service, String schema, String host, String path, String ak, String sk) { + this.region = region; + this.service = service; + this.host = host; + this.schema = schema; + this.path = path; + this.ak = ak; + this.sk = sk; + } + +// public static void main(String[] args) throws Exception { +// String SecretAccessKey = "T0dJeU1HVTRaamsxWldSaE5HTTNOemswWVdRMk1qUmxaVEprWXpFNFpUYw=="; +// String AccessKeyID = "AKLTNzZjODdkZGQ5YjkwNDhlZmFlNDFkNWQ1ODM4YmI4NmU"; +// // 请求地址 +// String endpoint = "visual.volcengineapi.com"; +// String path = "/"; // 路径,不包含 Query// 请求接口信息 +// String service = "cv"; +// String region = "cn-north-1"; +// String schema = "https"; +// Sign sign = new Sign(region, service, schema, endpoint, path, AccessKeyID, SecretAccessKey); +// +// String action = "CVProcess"; +// String version = "2022-08-31"; +// +// Date date = new Date(); +// HashMap queryMap = new HashMap<>() {{ +// put("Limit", "1"); +// }}; +// +// sign.doRequest("POST", queryMap, null, date, action, version); +// } + + + public static String invokeChangeClothesApi(byte[] body) throws Exception { + String SecretAccessKey = "T0dJeU1HVTRaamsxWldSaE5HTTNOemswWVdRMk1qUmxaVEprWXpFNFpUYw=="; + String AccessKeyID = "AKLTNzZjODdkZGQ5YjkwNDhlZmFlNDFkNWQ1ODM4YmI4NmU"; + // 请求地址 + String endpoint = "visual.volcengineapi.com"; + String path = "/"; // 路径,不包含 Query// 请求接口信息 + String service = "cv"; + String region = "cn-north-1"; + String schema = "https"; + Sign sign = new Sign(region, service, schema, endpoint, path, AccessKeyID, SecretAccessKey); + + String action = "CVProcess"; + String version = "2022-08-31"; + + Date date = new Date(); + HashMap queryMap = new HashMap<>() {{ + put("Limit", "1"); + }}; + + return sign.doRequest("POST", queryMap, body, date, action, version); + } + + + public String doRequest(String method, Map queryList, byte[] body, + Date date, String action, String version) throws Exception { + if (body == null) { + body = new byte[0]; + } + String xContentSha256 = hashSHA256(body); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + String xDate = sdf.format(date); + String shortXDate = xDate.substring(0, 8); + String contentType = "application/json"; + + String signHeader = "host;x-date;x-content-sha256;content-type"; + + + SortedMap realQueryList = new TreeMap<>(queryList); + realQueryList.put("Action", action); + realQueryList.put("Version", version); + StringBuilder querySB = new StringBuilder(); + for (String key : realQueryList.keySet()) { + querySB.append(signStringEncoder(key)).append("=").append(signStringEncoder(realQueryList.get(key))).append("&"); + } + querySB.deleteCharAt(querySB.length() - 1); + + String canonicalStringBuilder = method + "\n" + path + "\n" + querySB + "\n" + + "host:" + host + "\n" + + "x-date:" + xDate + "\n" + + "x-content-sha256:" + xContentSha256 + "\n" + + "content-type:" + contentType + "\n" + + "\n" + + signHeader + "\n" + + xContentSha256; + + System.out.println(canonicalStringBuilder); + + String hashcanonicalString = hashSHA256(canonicalStringBuilder.getBytes()); + String credentialScope = shortXDate + "/" + region + "/" + service + "/request"; + String signString = "HMAC-SHA256" + "\n" + xDate + "\n" + credentialScope + "\n" + hashcanonicalString; + + byte[] signKey = genSigningSecretKeyV4(sk, shortXDate, region, service); + String signature = HexFormat.of().formatHex(hmacSHA256(signKey, signString)); + + + URL url = new URL(schema + "://" + host + path + "?" + querySB); + + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod(method); + conn.setRequestProperty("Host", host); + conn.setRequestProperty("X-Date", xDate); + conn.setRequestProperty("X-Content-Sha256", xContentSha256); + conn.setRequestProperty("Content-Type", contentType); + conn.setRequestProperty("Authorization", "HMAC-SHA256" + + " Credential=" + ak + "/" + credentialScope + + ", SignedHeaders=" + signHeader + + ", Signature=" + signature); + if (!Objects.equals(conn.getRequestMethod(), "GET")) { + conn.setDoOutput(true); + OutputStream os = conn.getOutputStream(); + os.write(body); + os.flush(); + os.close(); + } + conn.connect(); + + int responseCode = conn.getResponseCode(); + + InputStream is; + if (responseCode == 200) { + is = conn.getInputStream(); + } else { + is = conn.getErrorStream(); + } + String responseBody = new String(is.readAllBytes()); + is.close(); + + System.out.println(responseCode); + System.out.println(responseBody); + return responseBody; + } + + private String signStringEncoder(String source) { + if (source == null) { + return null; + } + StringBuilder buf = new StringBuilder(source.length()); + ByteBuffer bb = UTF_8.encode(source); + while (bb.hasRemaining()) { + int b = bb.get() & 255; + if (URLENCODER.get(b)) { + buf.append((char) b); + } else if (b == 32) { + buf.append("%20"); + } else { + buf.append("%"); + char hex1 = CONST_ENCODE.charAt(b >> 4); + char hex2 = CONST_ENCODE.charAt(b & 15); + buf.append(hex1); + buf.append(hex2); + } + } + + return buf.toString(); + } + + public static String hashSHA256(byte[] content) throws Exception { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + + return HexFormat.of().formatHex(md.digest(content)); + } catch (Exception e) { + throw new Exception( + "Unable to compute hash while signing request: " + + e.getMessage(), e); + } + } + + public static byte[] hmacSHA256(byte[] key, String content) throws Exception { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + return mac.doFinal(content.getBytes()); + } catch (Exception e) { + throw new Exception( + "Unable to calculate a request signature: " + + e.getMessage(), e); + } + } + + private byte[] genSigningSecretKeyV4(String secretKey, String date, String region, String service) throws Exception { + byte[] kDate = hmacSHA256((secretKey).getBytes(), date); + byte[] kRegion = hmacSHA256(kDate, region); + byte[] kService = hmacSHA256(kRegion, service); + return hmacSHA256(kService, "request"); + } +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index d2d421f..fa52f6e 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -47,7 +47,7 @@ server: session: cookie: max-age: 2592000 - timeout: 720h + timeout: 2592000 mybatis-plus: mapper-locations: classpath:mapper/*.xml diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index be07b0a..ece4e14 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -52,7 +52,7 @@ server: session: cookie: max-age: 2592000 - timeout: 720h + timeout: 2592000 mybatis-plus: mapper-locations: classpath:mapper/*.xml diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 18684a4..1ab53e5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: profiles: - active: prod + active: dev