From 4ebe4839fba51e6c3453b05761adf4159833f3fa Mon Sep 17 00:00:00 2001
From: chen-xin-zhi <3588068430@qq.com>
Date: Sat, 3 May 2025 11:55:10 +0800
Subject: [PATCH] this is 4.22 update

---
 .../controller/clothes/ClothesController.java |  58 +++-
 .../model/dto/facelift/ClothesUrlObj.java     |  24 ++
 .../model/dto/facelift/GarmentObj.java        |  31 +++
 .../model/dto/facelift/LogoUrlObj.java        |  39 +++
 .../heritage/model/dto/facelift/ModelObj.java |  31 +++
 .../com/cultural/heritage/utils/Sign.java     | 258 ++++++++++++++++++
 src/main/resources/application-dev.yml        |   2 +-
 src/main/resources/application-prod.yml       |   2 +-
 src/main/resources/application.yml            |   2 +-
 9 files changed, 440 insertions(+), 7 deletions(-)
 create mode 100644 src/main/java/com/cultural/heritage/model/dto/facelift/ClothesUrlObj.java
 create mode 100644 src/main/java/com/cultural/heritage/model/dto/facelift/GarmentObj.java
 create mode 100644 src/main/java/com/cultural/heritage/model/dto/facelift/LogoUrlObj.java
 create mode 100644 src/main/java/com/cultural/heritage/model/dto/facelift/ModelObj.java
 create mode 100644 src/main/java/com/cultural/heritage/utils/Sign.java

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<String> invokeChangeClothesApi(@RequestParam String modelURL, @RequestParam String clothesURL) {
+        if (StringUtils.isBlank(modelURL) || StringUtils.isBlank(clothesURL)) {
+            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
+        }
+        Map<String, Object> 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<ClothesUrlObj> 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<ClothesUrlObj> 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.
+ * <p>
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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<String, String> 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<String, String> queryMap = new HashMap<>() {{
+            put("Limit", "1");
+        }};
+
+        return sign.doRequest("POST", queryMap, body, date, action, version);
+    }
+
+
+    public String doRequest(String method, Map<String, String> 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<String, String> 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