Quellcode durchsuchen

添加coze,实现图片识别功能

zhouzeyu vor 5 Monaten
Ursprung
Commit
061f017325

+ 22 - 0
tr-dependencies/pom.xml

@@ -88,6 +88,10 @@
         <jakarta-json.version>2.0.1</jakarta-json.version>
 
         <wx.version>4.7.0</wx.version>
+
+        <coze.version>LATEST</coze.version>
+
+        <spring-boot.version>2.7.8</spring-boot.version>
     </properties>
 
 
@@ -552,6 +556,24 @@
                 <version>${wx.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-data-redis</artifactId>
+                <version>${spring-boot.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.coze</groupId>
+                <artifactId>coze-api</artifactId>
+                <version>${coze.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>com.squareup.okhttp3</groupId>
+                        <artifactId>okhttp</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 </project>

+ 10 - 0
tr-modules/tr-module-smartFollowUp/pom.xml

@@ -64,6 +64,16 @@
             <version>0.0.9</version>
         </dependency>
 
+        <dependency>
+            <groupId>com.coze</groupId>
+            <artifactId>coze-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
 
         <dependency>
             <groupId>cn.tr</groupId>

+ 43 - 0
tr-modules/tr-module-smartFollowUp/src/main/java/cn/tr/module/smart/coze/config/CozeConfig.java

@@ -0,0 +1,43 @@
+package cn.tr.module.smart.coze.config;
+
+import cn.tr.module.smart.coze.properties.CozeProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author zzy
+ * @Data 2025/7/2
+ * @Version 1.0
+ * @Description XXX
+ */
+@Configuration
+@EnableCaching
+@EnableConfigurationProperties(CozeProperties.class)
+public class CozeConfig {
+    @Bean
+    public RestTemplate cozeRestTemplate(CozeProperties properties) {
+        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory(properties));
+        // 添加JSON消息转换器
+        List<HttpMessageConverter<?>> converters = new ArrayList<>();
+        converters.add(new MappingJackson2HttpMessageConverter());
+        restTemplate.setMessageConverters(converters);
+        return restTemplate;
+    }
+
+    private ClientHttpRequestFactory getClientHttpRequestFactory(CozeProperties properties) {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setConnectTimeout(properties.getTimeout());
+        factory.setReadTimeout(properties.getTimeout());
+        return factory;
+    }
+}

+ 73 - 0
tr-modules/tr-module-smartFollowUp/src/main/java/cn/tr/module/smart/coze/config/CozeOAuthTokenConfig.java

@@ -0,0 +1,73 @@
+package cn.tr.module.smart.coze.config;
+
+import cn.tr.module.smart.coze.properties.CozeProperties;
+import cn.tr.module.sys.tenant.dto.OAuthTokenAddCacheDTO;
+import cn.tr.module.sys.tenant.util.CacheUtil;
+import com.coze.openapi.client.auth.OAuthToken;
+import com.coze.openapi.service.auth.JWTOAuthClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+import java.util.Date;
+
+/**
+ * @Author zzy
+ * @Data 2025/7/15
+ * @Version 1.0
+ * @Description XXX
+ */
+@Configuration
+@Slf4j
+public class CozeOAuthTokenConfig {
+
+    @Resource
+    private CozeProperties cozeProperties;
+    @Resource
+    private CacheUtil cacheUtil;
+
+    public OAuthTokenAddCacheDTO getAccessToken() {
+        // 从缓存中获取token
+        OAuthTokenAddCacheDTO token = cacheUtil.getToken();
+        if (token != null) {
+            return token;
+        }
+        //构建JWT认证客户端
+        JWTOAuthClient oauth = buildJwtOAuthClient();
+        // 全新获取令牌
+        try {
+            OAuthToken newToken = oauth.getAccessToken();
+            OAuthTokenAddCacheDTO newCacheToken = OAuthTokenAddCacheDTO.builder()
+                    .accessToken(newToken.getAccessToken())
+                    .refreshToken(newToken.getRefreshToken())
+                    .tokenType(newToken.getTokenType())
+                    .expiresIn(newToken.getExpiresIn())
+//                    .generateTime(new Date())
+                    .build();
+
+            return cacheUtil.setToken(newCacheToken);
+        } catch (Exception e) {
+            throw new RuntimeException("无法获取Coze访问令牌", e);
+        }
+    }
+
+    private JWTOAuthClient buildJwtOAuthClient() {
+        try {
+            return new JWTOAuthClient.JWTOAuthBuilder()
+                    .clientID(cozeProperties.getClientId())
+                    .privateKey(cozeProperties.getPrivateKey())
+                    .publicKey(cozeProperties.getPublicKey())
+                    .baseURL(cozeProperties.getBaseUrl())
+                    .jwtBuilder(new ExampleJWTBuilder())
+                    .build();
+        } catch (Exception e) {
+            throw new RuntimeException("构建JWT认证客户端失败",e);
+        }
+    }
+
+
+}
+
+
+

+ 44 - 0
tr-modules/tr-module-smartFollowUp/src/main/java/cn/tr/module/smart/coze/config/ExampleJWTBuilder.java

@@ -0,0 +1,44 @@
+package cn.tr.module.smart.coze.config;
+
+import com.coze.openapi.service.auth.JWTBuilder;
+import com.coze.openapi.service.auth.JWTPayload;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.NoArgsConstructor;
+
+import java.security.PrivateKey;
+import java.util.Map;
+
+
+/**
+ * @Author zzy
+ * @Data 2025/7/15
+ * @Version 1.0
+ * @Description XXX
+ */
+@NoArgsConstructor
+public class ExampleJWTBuilder implements JWTBuilder {
+    @Override
+    public String generateJWT(PrivateKey privateKey, Map<String, Object> header, JWTPayload payload) {
+        try {
+            JwtBuilder jwtBuilder =
+                    Jwts.builder()
+                            .setHeader(header)
+                            .setIssuer(payload.getIss())
+                            .setAudience(payload.getAud())
+                            .setIssuedAt(payload.getIat())
+                            .setExpiration(payload.getExp())
+                            .setId(payload.getJti())
+
+                            .signWith(privateKey, SignatureAlgorithm.RS256);
+            if (payload.getSessionName() != null) {
+                jwtBuilder.claim("session_name", payload.getSessionName());
+            }
+            return jwtBuilder.compact();
+
+        } catch (Exception e) {
+            throw new RuntimeException("生成JWT失败", e);
+        }
+    }
+}

+ 42 - 0
tr-modules/tr-module-smartFollowUp/src/main/java/cn/tr/module/smart/coze/controller/CozeController.java

@@ -0,0 +1,42 @@
+package cn.tr.module.smart.coze.controller;
+
+import cn.tr.core.pojo.CommonResult;
+import cn.tr.module.smart.coze.service.CozeService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+
+/**
+ * @Author zzy
+ * @Data 2025/7/2
+ * @Version 1.0
+ * @Description XXX
+ */
+@RestController
+@RequestMapping("/oauth/coze")
+@Slf4j
+@Api(tags = "识别患者图片信息")
+public class CozeController {
+
+    @Autowired
+    private CozeService cozeService;
+
+    @PostMapping("/recognize")
+    @ApiOperation("识别患者图片信息")
+    public CommonResult<Map<String, Object>> recognizeImage(@RequestParam MultipartFile file,
+                                                            @RequestParam(required = false) String configId,
+                                                            @RequestParam(required = false) String cateId) throws Exception {
+        return cozeService.recognizeImage(file, configId, cateId);
+
+    }
+
+
+}

+ 50 - 0
tr-modules/tr-module-smartFollowUp/src/main/java/cn/tr/module/smart/coze/properties/CozeProperties.java

@@ -0,0 +1,50 @@
+package cn.tr.module.smart.coze.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @Author zzy
+ * @Data 2025/7/2
+ * @Version 1.0
+ * @Description XXX
+ */
+@Data
+@ConfigurationProperties(prefix = "coze.api")
+public class CozeProperties {
+    private String clientId;
+    private String publicKey;
+    private String redirectUri;
+    private String baseUrl;
+    private String workflowId;
+    private int timeout;
+    private String maxFileSize;
+    private String[] allowedFileTypes;
+    private String imageParamName;
+
+    public String getPrivateKey() {
+        try (InputStream inputStream = getClass().getClassLoader()
+                .getResourceAsStream("private_key.pem")) {
+
+            if (inputStream == null) {
+                throw new RuntimeException("私钥文件未找到");
+            }
+            // 手动读取流数据
+            ByteArrayOutputStream result = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) != -1) {
+                result.write(buffer, 0, length);
+            }
+            return result.toString(StandardCharsets.UTF_8.name());
+
+        } catch (IOException e) {
+            throw new RuntimeException("读取私钥文件失败", e);
+        }
+    }
+}

+ 18 - 0
tr-modules/tr-module-smartFollowUp/src/main/java/cn/tr/module/smart/coze/service/CozeService.java

@@ -0,0 +1,18 @@
+package cn.tr.module.smart.coze.service;
+
+import cn.tr.core.pojo.CommonResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+
+/**
+ * @Author zzy
+ * @Data 2025/7/2
+ * @Version 1.0
+ * @Description XXX
+ */
+public interface CozeService {
+    CommonResult<Map<String, Object>> recognizeImage(MultipartFile file,String configId,String cateId) throws Exception;
+
+
+}

+ 139 - 0
tr-modules/tr-module-smartFollowUp/src/main/java/cn/tr/module/smart/coze/service/impl/CozeServiceimpl.java

@@ -0,0 +1,139 @@
+package cn.tr.module.smart.coze.service.impl;
+
+import cn.tr.core.pojo.CommonResult;
+import cn.tr.module.smart.coze.config.CozeOAuthTokenConfig;
+import cn.tr.module.smart.coze.properties.CozeProperties;
+import cn.tr.module.smart.coze.service.CozeService;
+import cn.tr.module.sys.storage.dto.SysStorageRecordDTO;
+import cn.tr.module.sys.storage.service.IStorageFileService;
+import cn.tr.module.sys.tenant.dto.OAuthTokenAddCacheDTO;
+import com.coze.openapi.client.workflows.run.RunWorkflowReq;
+import com.coze.openapi.client.workflows.run.RunWorkflowResp;
+import com.coze.openapi.service.auth.TokenAuth;
+import com.coze.openapi.service.service.CozeAPI;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.unit.DataSize;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * @Author zzy
+ * @Data 2025/7/2
+ * @Version 1.0
+ * @Description XXX
+ */
+@Service
+@Slf4j
+public class CozeServiceimpl implements CozeService {
+
+    @Autowired
+    private CozeProperties cozeProperties;
+    @Autowired
+    private IStorageFileService fileService;
+
+    @Autowired
+    private CozeOAuthTokenConfig cozeOAuthTokenConfig;
+
+    public CommonResult<Map<String, Object>> recognizeImage(MultipartFile file, String configId, String cateId) throws Exception {
+
+        // 验证文件
+        validateImageFile(file);
+
+        String imageUrl;
+        SysStorageRecordDTO uploadResult = fileService.upload(
+                configId,
+                cateId,
+                file.getOriginalFilename(),
+                file.getBytes(),
+                null);
+
+        imageUrl = uploadResult.getAbsolutePath();
+        //获取token
+        OAuthTokenAddCacheDTO oauthToken = cozeOAuthTokenConfig.getAccessToken();
+        String accessToken = oauthToken.getAccessToken();
+        TokenAuth tokenAuth = new TokenAuth(accessToken);
+
+        CozeAPI coze = new CozeAPI.Builder()
+                .baseURL(cozeProperties.getBaseUrl())
+                .auth(tokenAuth)
+                .readTimeout(60000) // 将超时时间设置为60000毫秒(60秒)
+                .build();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put(cozeProperties.getImageParamName(), imageUrl);
+
+        // 执行工作流
+        RunWorkflowResp workflowResp = coze.workflows().runs().create(
+                RunWorkflowReq.builder()
+                        .workflowID(cozeProperties.getWorkflowId())
+                        .parameters(parameters)
+                        .build()
+        );
+        String data = workflowResp.getData();
+        ObjectMapper objectMapper = new ObjectMapper();
+        Map<String, Object> responseMap = objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
+        });
+
+        String outputJson = (String) responseMap.get("output");
+
+        List<Map<String, Object>> patientList = objectMapper.readValue(
+                outputJson,
+                new TypeReference<List<Map<String, Object>>>() {
+                }
+        );
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("count", patientList.size());
+        result.put("patients", patientList);
+        return CommonResult.success(result);
+
+    }
+
+
+
+
+    private void validateImageFile(MultipartFile file) throws IOException {
+        // 检查文件大小
+        long maxSize = DataSize.parse(cozeProperties.getMaxFileSize()).toBytes();
+        if (file.getSize() > maxSize) {
+            throw new IOException("文件大小超过限制: " + cozeProperties.getMaxFileSize());
+        }
+
+        // 检查文件类型
+        String fileName = file.getOriginalFilename();
+        if (fileName == null) {
+            throw new IOException("无效的文件名");
+        }
+
+        String extension = getFileExtension(fileName);
+        if (!isValidFileType(extension)) {
+            throw new IOException("不支持的文件类型. 支持的类型: " +
+                    String.join(", ", cozeProperties.getAllowedFileTypes()));
+        }
+    }
+
+    private String getFileExtension(String fileName) {
+        int dotIndex = fileName.lastIndexOf(".");
+        if (dotIndex > 0 && dotIndex < fileName.length() - 1) {
+            return fileName.substring(dotIndex + 1).toLowerCase();
+        }
+        return "";
+    }
+
+    private boolean isValidFileType(String extension) {
+        for (String allowedType : cozeProperties.getAllowedFileTypes()) {
+            if (allowedType.equalsIgnoreCase(extension)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 28 - 0
tr-modules/tr-module-smartFollowUp/src/main/resources/private_key.pem

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCo7r7ufPndk7WY
+TFb5b6c5rXJDyEIy5J4rm0BKcxPdZi/wezfYyopEPxSevQHHKqD4Z/ELalcQ7YVc
+kg92txt5iPH479JKz5Q9VhqqzPGYGKuuwCii+oZ43BsmbmApCIcU4GePfBMcwJ78
+LL1Rcx2akloGh23OAfRPngoPy4o/tcICDJysYx/7A1/eLohHqjRXVSN5DP9lWQxR
+VzVsGec+HSXASmf5pYLrbAvcvEHfkmFzJDUQnvqMnvjHFVLDX4nSKo3iu2St8mWt
+Pxy7+70E/AWYT8tTO2eRj3KZTxEZE0TrLkxDO9K55bz+QN50bDUt7ASRjASVmCht
+Eezl4l/NAgMBAAECggEACXBTgzRA6sA0yoZjaqryIz8dbgubpUfQTZvuTuqYnst1
+nMBpuadxa8hBgPALECOB3HdESNC/fh7jT22KxyWKd7Mu3+uYap0oCXhM46OxUP/5
+4bjzXE3WoxybOL28ijZjg8dZCfOTBs19ZJYkWs9thUQtmwVInZTO337zTXggKJiK
+EPFz5zxHVEUgX7s5Hwf8bnFg7Anzl2xLHsufnR3d9q+gdppCOzciZfjfXL6ciYMu
+hWxRSz61Ew6kLNSF7igOA5Za1NFgihBO9ITJBxkRjqful6wUTBGp6b9PH/CoBWim
+30tmFf+t0LwQety7Of2nMk/BLOhgCpsDtp19BPJdMQKBgQDp5aYnNgyDoQ3Ef5BH
+5eheFvD7NGZ1OWoZvSw1tzwWmr+Tqa5p4jflLi9Bt/3sqxJTG7uNZXj1yfzFBgfw
+1LxfmtY1EjQ7K6IFziaurOIATVROtPj31jJeA4Sc1mZx9aGRZjizDo4MyvM8P7rL
++D+m3G4fjlkLNE1FjREVx6MoMQKBgQC45YAlDOX+3xMm8PRbvA7zz8+1cY9xNFA6
+HCWoLW5RPMfp6qd4Pzer0aryi2BDnRK6mNuOuR0DOKE9+xC4WSYz8QHbRo9IK2gX
+PrxRVVZ608Ku273g4jfTvxHsKw7L17MwZz7N+p2r5bE+Gxb+UUGseGazxAM+zc81
+DCN45h2mXQKBgEB1XnXd6lL4NoAZm5yE2qXbcqv4A+h4WyoevSlMhw2/td4u2/co
+Nbk+Ih3dY+guOQ2YTfaoqU4rTTLK97NCHWvHkxLrImPQIYWyC20GDf6BUSOjsh0y
+9Yx9MbW7TF5JkC4u2p9V+oXCBIhtE8CUeI42n06o/xccYdMyDixPaUJxAoGAUExr
+gKu/XxcmTC0tEoHzxHMl89jjwPhFN1duC0HfvSw2biJYpOJfnSErqrZZEkQvFBa6
+k5tVPEblz/MvacBd1QUAF2jnZSJkzGOUiYYUTreUvzfzmKzmLfG3KOfyPxjoW0cb
+gX0r6LGSbjR5oe1MJkkL4VppRmc/a/xfk2vsyIkCgYEAmSpdfJ451c6ftSQRR6Cl
+sYMwZL+SCdcqirAkd0dhmK6T228VfAVohE+2/dDFMgcVv1cg4ktQdv6TOj9d2f+R
+vbN3D6dNAcACmOWiCWtO/51nIbQbFyqPNAiHx75r6w/u7l1jqIrsjWR67uQJl8Ks
+fKrHCoUOtZR5YSCpPXMNCXo=
+-----END PRIVATE KEY-----

+ 36 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/tenant/dto/OAuthTokenAddCacheDTO.java

@@ -0,0 +1,36 @@
+package cn.tr.module.sys.tenant.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @Author zzy
+ * @Data 2025/7/15
+ * @Version 1.0
+ * @Description XXX
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("添加CozeToken缓存对象")
+public class OAuthTokenAddCacheDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "访问令牌" )
+    private String accessToken;
+    @ApiModelProperty(value = "过期时间" )
+    private Integer expiresIn;
+    @ApiModelProperty(value = "刷新令牌" )
+    private String refreshToken;
+    @ApiModelProperty(value = "类型" )
+    private String tokenType;
+
+
+}

+ 22 - 0
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/tenant/util/CacheUtil.java

@@ -1,5 +1,6 @@
 package cn.tr.module.sys.tenant.util;
 
+import cn.tr.module.sys.tenant.dto.OAuthTokenAddCacheDTO;
 import cn.tr.module.sys.tenant.dto.SysAdminAddCacheDTO;
 import org.springframework.cache.annotation.CachePut;
 import org.springframework.cache.annotation.Cacheable;
@@ -35,4 +36,25 @@ public class CacheUtil {
     public String getPhoneCode(){
         return "123";
     }
+
+
+    @CachePut(value = "global:coze:token#900",key="'coze_access_token'")
+    public OAuthTokenAddCacheDTO setToken(OAuthTokenAddCacheDTO token){
+        return OAuthTokenAddCacheDTO.builder()
+                .accessToken(token.getAccessToken())
+                .refreshToken(token.getRefreshToken())
+                .tokenType(token.getTokenType())
+                .expiresIn(token.getExpiresIn())
+                .build();
+    }
+
+    @Cacheable(value = "global:coze:token#900",key="'coze_access_token'")
+    public OAuthTokenAddCacheDTO getToken(){
+        return null;
+    }
+
+    @CachePut(value = "global:coze:token#900",key="'coze_access_token'")
+    public OAuthTokenAddCacheDTO updateToken(OAuthTokenAddCacheDTO tokenDTO) {
+        return tokenDTO;
+    }
 }

+ 15 - 1
tr-test/src/main/resources/application-dev.yml

@@ -40,4 +40,18 @@ wx:
         secret: 90bbf63f5d88e3c2c443de105dd94fd5  #微信小程序的Secret
         token: smart #微信小程序消息服务器配置的token
         aesKey: 4ywJtjRvvpFONIVN8GxPhSpi2eQraoIDZgUwn8Jmm2C #微信小程序消息服务器配置的EncodingAESKey
-        msgDataFormat: JSON
+        msgDataFormat: JSON
+
+
+# 添加Coze配置
+coze:
+  api:
+    clientId: 1133219873523
+    publicKey: 56rfqoy4WCOsSlhi_BNafW5QmD7g7ISPit4SRG0jvTk
+    redirectUri: http://localhost:8083/oauth/coze/recognize
+    baseUrl: https://api.coze.cn/v1/workflow/run/
+    workflowId: 7526811461625905198
+    timeout: 5000
+    max-file-size: 5MB
+    allowed-file-types: jpg,jpeg,png,webp
+    image-param-name: input