增加紫鸟相关接口
This commit is contained in:
@@ -4,6 +4,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties({OssProperties.class, StorageProperties.class, BrandProgressProperties.class})
|
@EnableConfigurationProperties({OssProperties.class, StorageProperties.class, BrandProgressProperties.class, ZiniaoProperties.class})
|
||||||
public class PropertiesConfig {
|
public class PropertiesConfig {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.nanri.aiimage.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties(prefix = "aiimage.ziniao")
|
||||||
|
public class ZiniaoProperties {
|
||||||
|
private boolean enabled;
|
||||||
|
private String baseUrl;
|
||||||
|
private String apiKey;
|
||||||
|
private Long companyId;
|
||||||
|
private String shopsPath;
|
||||||
|
private String switchShopPath;
|
||||||
|
private String openStoreScheme;
|
||||||
|
private String openStoreUserId;
|
||||||
|
private String openStoreLaunchUrl;
|
||||||
|
private Boolean openStoreAutoOpen;
|
||||||
|
private Integer openStoreDebugPort;
|
||||||
|
private Boolean openStoreNotPromptForDownload;
|
||||||
|
private String openStoreForceDownloadPath;
|
||||||
|
private String openStoreExtraArgs;
|
||||||
|
private long sessionTtlHours;
|
||||||
|
private long shopsCacheMinutes;
|
||||||
|
private int connectTimeoutSeconds;
|
||||||
|
private int readTimeoutSeconds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.client;
|
||||||
|
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopCacheDto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ZiniaoClient {
|
||||||
|
List<ZiniaoShopCacheDto> listShops();
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.client;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.nanri.aiimage.common.exception.BusinessException;
|
||||||
|
import com.nanri.aiimage.config.ZiniaoProperties;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopCacheDto;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ZiniaoClientImpl implements ZiniaoClient {
|
||||||
|
|
||||||
|
private final ZiniaoProperties ziniaoProperties;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ZiniaoShopCacheDto> listShops() {
|
||||||
|
if (ziniaoProperties.getCompanyId() == null || ziniaoProperties.getCompanyId() <= 0) {
|
||||||
|
throw new BusinessException("紫鸟 companyId 未配置");
|
||||||
|
}
|
||||||
|
String raw = getRestClient().post()
|
||||||
|
.uri(joinUrl(ziniaoProperties.getBaseUrl(), ziniaoProperties.getShopsPath()))
|
||||||
|
.headers(headers -> {
|
||||||
|
headers.setBearerAuth(ziniaoProperties.getApiKey());
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
})
|
||||||
|
.body(java.util.Map.of("companyId", ziniaoProperties.getCompanyId()))
|
||||||
|
.retrieve()
|
||||||
|
.body(String.class);
|
||||||
|
return parseShops(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestClient getRestClient() {
|
||||||
|
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||||
|
requestFactory.setConnectTimeout(ziniaoProperties.getConnectTimeoutSeconds() * 1000);
|
||||||
|
requestFactory.setReadTimeout(ziniaoProperties.getReadTimeoutSeconds() * 1000);
|
||||||
|
return RestClient.builder().requestFactory(requestFactory).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ZiniaoShopCacheDto> parseShops(String raw) {
|
||||||
|
try {
|
||||||
|
JsonNode root = objectMapper.readTree(raw);
|
||||||
|
String code = text(root.get("code"));
|
||||||
|
if (code != null && !"0".equals(code)) {
|
||||||
|
throw new BusinessException("紫鸟店铺接口返回失败: " + Objects.toString(text(root.get("msg")), "未知错误"));
|
||||||
|
}
|
||||||
|
JsonNode wrapper = root.get("data");
|
||||||
|
if (wrapper != null && wrapper.isObject()) {
|
||||||
|
String status = text(wrapper.get("status"));
|
||||||
|
Long ret = longValue(wrapper.get("ret"));
|
||||||
|
if ((ret != null && ret != 0L) || (status != null && !"success".equalsIgnoreCase(status))) {
|
||||||
|
throw new BusinessException("紫鸟店铺接口业务失败: " + Objects.toString(status, "unknown"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonNode itemsNode = wrapper == null ? null : wrapper.get("data");
|
||||||
|
List<ZiniaoShopCacheDto> items = new ArrayList<>();
|
||||||
|
if (itemsNode != null && itemsNode.isArray()) {
|
||||||
|
for (JsonNode itemNode : itemsNode) {
|
||||||
|
ZiniaoShopCacheDto item = new ZiniaoShopCacheDto();
|
||||||
|
item.setShopId(text(firstNonNull(itemNode.get("shopId"), itemNode.get("id"), itemNode.get("accountId"), itemNode.get("account_id"))));
|
||||||
|
item.setShopName(text(firstNonNull(itemNode.get("shopName"), itemNode.get("name"), itemNode.get("accountName"), itemNode.get("account_name"))));
|
||||||
|
item.setPlatform(text(firstNonNull(itemNode.get("platform"), itemNode.get("platformName"), itemNode.get("site"), itemNode.get("siteName"))));
|
||||||
|
if (item.getShopId() != null && !item.getShopId().isBlank()) {
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
} catch (BusinessException ex) {
|
||||||
|
throw ex;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("解析紫鸟店铺列表失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String joinUrl(String baseUrl, String path) {
|
||||||
|
String base = Objects.toString(baseUrl, "").trim();
|
||||||
|
String suffix = Objects.toString(path, "").trim();
|
||||||
|
if (base.endsWith("/") && suffix.startsWith("/")) {
|
||||||
|
return base + suffix.substring(1);
|
||||||
|
}
|
||||||
|
if (!base.endsWith("/") && !suffix.startsWith("/")) {
|
||||||
|
return base + "/" + suffix;
|
||||||
|
}
|
||||||
|
return base + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode firstNonNull(JsonNode... nodes) {
|
||||||
|
for (JsonNode node : nodes) {
|
||||||
|
if (node != null && !node.isNull()) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String text(JsonNode node) {
|
||||||
|
if (node == null || node.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String value = node.asText();
|
||||||
|
return value == null || value.isBlank() ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long longValue(JsonNode node) {
|
||||||
|
if (node == null || node.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return node.asLong();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.controller;
|
||||||
|
|
||||||
|
import com.nanri.aiimage.common.api.ApiResponse;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.dto.ZiniaoSwitchShopRequest;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoCurrentShopVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoLoginUrlVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoSessionVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopListVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoSwitchShopVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
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.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/api/ziniao")
|
||||||
|
@Tag(name = "紫鸟接入", description = "紫鸟 ApiKey 模式下的配置状态、店铺列表、当前店铺与切换店铺接口")
|
||||||
|
public class ZiniaoAuthController {
|
||||||
|
|
||||||
|
private final ZiniaoAuthService ziniaoAuthService;
|
||||||
|
|
||||||
|
@GetMapping("/login-url")
|
||||||
|
@Operation(summary = "获取紫鸟接入信息", description = "兼容保留接口,返回当前 ApiKey 模式的接入信息和 sessionId。")
|
||||||
|
@ApiResponses({
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "获取成功", content = @Content(schema = @Schema(implementation = ZiniaoLoginUrlVo.class)))
|
||||||
|
})
|
||||||
|
public ApiResponse<ZiniaoLoginUrlVo> getLoginUrl() {
|
||||||
|
return ApiResponse.success(ziniaoAuthService.getLoginUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/callback")
|
||||||
|
@Operation(summary = "兼容保留回调接口", description = "ApiKey 模式下无真实登录回调,该接口仅兼容旧调用并返回 302。")
|
||||||
|
@ApiResponses({
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "302", description = "重定向到兼容地址")
|
||||||
|
})
|
||||||
|
public ResponseEntity<Void> callback(@RequestParam(required = false) String code, @RequestParam(required = false) String state) {
|
||||||
|
try {
|
||||||
|
String redirectUrl = ziniaoAuthService.handleCallback(code, state);
|
||||||
|
return ResponseEntity.status(302)
|
||||||
|
.header(HttpHeaders.LOCATION, redirectUrl)
|
||||||
|
.build();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return ResponseEntity.status(302)
|
||||||
|
.header(HttpHeaders.LOCATION, ziniaoAuthService.buildFailureRedirect(ex.getMessage()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/session")
|
||||||
|
@Operation(summary = "获取紫鸟接入状态", description = "根据 sessionId 获取当前 ApiKey 模式会话与当前店铺信息。")
|
||||||
|
@ApiResponses({
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "获取成功", content = @Content(schema = @Schema(implementation = ZiniaoSessionVo.class)))
|
||||||
|
})
|
||||||
|
public ApiResponse<ZiniaoSessionVo> getSession(@RequestParam String sessionId) {
|
||||||
|
return ApiResponse.success(ziniaoAuthService.getSession(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/shops")
|
||||||
|
@Operation(summary = "获取紫鸟店铺列表", description = "根据 sessionId 使用配置的 ApiKey 与 companyId 拉取最新店铺列表。")
|
||||||
|
@ApiResponses({
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "获取成功", content = @Content(schema = @Schema(implementation = ZiniaoShopListVo.class)))
|
||||||
|
})
|
||||||
|
public ApiResponse<ZiniaoShopListVo> listShops(@RequestParam String sessionId) {
|
||||||
|
return ApiResponse.success(ziniaoAuthService.listShops(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/shops/current")
|
||||||
|
@Operation(summary = "获取当前紫鸟店铺", description = "根据 sessionId 获取当前已选中的店铺。")
|
||||||
|
@ApiResponses({
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "获取成功", content = @Content(schema = @Schema(implementation = ZiniaoCurrentShopVo.class)))
|
||||||
|
})
|
||||||
|
public ApiResponse<ZiniaoCurrentShopVo> getCurrentShop(@RequestParam String sessionId) {
|
||||||
|
return ApiResponse.success(ziniaoAuthService.getCurrentShop(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/shops/switch")
|
||||||
|
@Operation(summary = "切换当前紫鸟店铺", description = "根据 sessionId 和 shopId 切换当前选中的店铺。")
|
||||||
|
@ApiResponses({
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "切换成功", content = @Content(schema = @Schema(implementation = ZiniaoSwitchShopVo.class)))
|
||||||
|
})
|
||||||
|
public ApiResponse<ZiniaoSwitchShopVo> switchShop(@Valid @RequestBody ZiniaoSwitchShopRequest request) {
|
||||||
|
return ApiResponse.success(ziniaoAuthService.switchShop(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.cache;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ZiniaoSessionCacheDto {
|
||||||
|
private String sessionId;
|
||||||
|
private String accessToken;
|
||||||
|
private String refreshToken;
|
||||||
|
private String tokenType;
|
||||||
|
private Long expireAt;
|
||||||
|
private String ziniaoUserId;
|
||||||
|
private String nickname;
|
||||||
|
private String defaultShopId;
|
||||||
|
}
|
||||||
11
backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopCacheDto.java
vendored
Normal file
11
backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/model/cache/ZiniaoShopCacheDto.java
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.cache;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ZiniaoShopCacheDto {
|
||||||
|
private String shopId;
|
||||||
|
private String shopName;
|
||||||
|
private String platform;
|
||||||
|
private Long selectedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ZiniaoSwitchShopRequest {
|
||||||
|
@NotBlank(message = "sessionId 不能为空")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@NotBlank(message = "shopId 不能为空")
|
||||||
|
private String shopId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "当前紫鸟店铺")
|
||||||
|
public class ZiniaoCurrentShopVo {
|
||||||
|
@Schema(description = "店铺 ID")
|
||||||
|
private String shopId;
|
||||||
|
|
||||||
|
@Schema(description = "店铺名称")
|
||||||
|
private String shopName;
|
||||||
|
|
||||||
|
@Schema(description = "平台")
|
||||||
|
private String platform;
|
||||||
|
|
||||||
|
@Schema(description = "选中时间戳(毫秒)")
|
||||||
|
private Long selectedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "紫鸟登录地址")
|
||||||
|
public class ZiniaoLoginUrlVo {
|
||||||
|
@Schema(description = "登录链接")
|
||||||
|
private String loginUrl;
|
||||||
|
|
||||||
|
@Schema(description = "登录 state")
|
||||||
|
private String state;
|
||||||
|
|
||||||
|
@Schema(description = "state 过期时间戳(毫秒)")
|
||||||
|
private Long expireAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "紫鸟会话信息")
|
||||||
|
public class ZiniaoSessionVo {
|
||||||
|
@Schema(description = "会话 ID")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@Schema(description = "是否已认证")
|
||||||
|
private Boolean authenticated;
|
||||||
|
|
||||||
|
@Schema(description = "紫鸟用户 ID")
|
||||||
|
private String ziniaoUserId;
|
||||||
|
|
||||||
|
@Schema(description = "紫鸟昵称")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
@Schema(description = "会话过期时间戳(毫秒)")
|
||||||
|
private Long expireAt;
|
||||||
|
|
||||||
|
@Schema(description = "店铺数量")
|
||||||
|
private Integer shopCount;
|
||||||
|
|
||||||
|
@Schema(description = "当前店铺 ID")
|
||||||
|
private String currentShopId;
|
||||||
|
|
||||||
|
@Schema(description = "当前店铺名称")
|
||||||
|
private String currentShopName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "紫鸟店铺项")
|
||||||
|
public class ZiniaoShopItemVo {
|
||||||
|
@Schema(description = "店铺 ID")
|
||||||
|
private String shopId;
|
||||||
|
|
||||||
|
@Schema(description = "店铺名称")
|
||||||
|
private String shopName;
|
||||||
|
|
||||||
|
@Schema(description = "平台")
|
||||||
|
private String platform;
|
||||||
|
|
||||||
|
@Schema(description = "是否为当前选中")
|
||||||
|
private Boolean selected;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "紫鸟店铺列表")
|
||||||
|
public class ZiniaoShopListVo {
|
||||||
|
@Schema(description = "店铺列表")
|
||||||
|
private List<ZiniaoShopItemVo> items = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "切换紫鸟店铺结果")
|
||||||
|
public class ZiniaoSwitchShopVo {
|
||||||
|
@Schema(description = "是否切换成功")
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
@Schema(description = "当前店铺 ID")
|
||||||
|
private String currentShopId;
|
||||||
|
|
||||||
|
@Schema(description = "当前店铺名称")
|
||||||
|
private String currentShopName;
|
||||||
|
|
||||||
|
@Schema(description = "紫鸟启动链接")
|
||||||
|
private String openStoreUrl;
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.service;
|
||||||
|
|
||||||
|
import com.nanri.aiimage.common.exception.BusinessException;
|
||||||
|
import com.nanri.aiimage.config.ZiniaoProperties;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.client.ZiniaoClient;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoSessionCacheDto;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopCacheDto;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.dto.ZiniaoSwitchShopRequest;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoCurrentShopVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoLoginUrlVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoSessionVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopItemVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopListVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoSwitchShopVo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ZiniaoAuthService {
|
||||||
|
|
||||||
|
private final ZiniaoProperties ziniaoProperties;
|
||||||
|
private final ZiniaoClient ziniaoClient;
|
||||||
|
private final ZiniaoSessionCacheService ziniaoSessionCacheService;
|
||||||
|
|
||||||
|
public ZiniaoLoginUrlVo getLoginUrl() {
|
||||||
|
ensureEnabled();
|
||||||
|
String sessionId = ensureSession();
|
||||||
|
ZiniaoLoginUrlVo vo = new ZiniaoLoginUrlVo();
|
||||||
|
vo.setState(sessionId);
|
||||||
|
vo.setExpireAt(Instant.now().plusSeconds(ziniaoProperties.getSessionTtlHours() * 3600).toEpochMilli());
|
||||||
|
vo.setLoginUrl(ziniaoProperties.getBaseUrl());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String handleCallback(String code, String state) {
|
||||||
|
ensureEnabled();
|
||||||
|
String sessionId = ensureSession();
|
||||||
|
return ziniaoProperties.getBaseUrl() + "?sessionId=" + sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZiniaoSessionVo getSession(String sessionId) {
|
||||||
|
ZiniaoSessionCacheDto session = requireSession(sessionId);
|
||||||
|
List<ZiniaoShopCacheDto> shops = ziniaoSessionCacheService.getShops(sessionId);
|
||||||
|
ZiniaoShopCacheDto currentShop = getOrInitCurrentShop(sessionId, shops);
|
||||||
|
ZiniaoSessionVo vo = new ZiniaoSessionVo();
|
||||||
|
vo.setSessionId(session.getSessionId());
|
||||||
|
vo.setAuthenticated(true);
|
||||||
|
vo.setZiniaoUserId(session.getZiniaoUserId());
|
||||||
|
vo.setNickname(session.getNickname());
|
||||||
|
vo.setExpireAt(session.getExpireAt());
|
||||||
|
vo.setShopCount(shops.size());
|
||||||
|
if (currentShop != null) {
|
||||||
|
vo.setCurrentShopId(currentShop.getShopId());
|
||||||
|
vo.setCurrentShopName(currentShop.getShopName());
|
||||||
|
}
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZiniaoShopListVo listShops(String sessionId) {
|
||||||
|
requireSession(sessionId);
|
||||||
|
List<ZiniaoShopCacheDto> shops = ziniaoClient.listShops();
|
||||||
|
ziniaoSessionCacheService.saveShops(sessionId, shops);
|
||||||
|
ZiniaoShopCacheDto currentShop = getOrInitCurrentShop(sessionId, shops);
|
||||||
|
ZiniaoShopListVo vo = new ZiniaoShopListVo();
|
||||||
|
for (ZiniaoShopCacheDto shop : shops) {
|
||||||
|
ZiniaoShopItemVo item = new ZiniaoShopItemVo();
|
||||||
|
item.setShopId(shop.getShopId());
|
||||||
|
item.setShopName(shop.getShopName());
|
||||||
|
item.setPlatform(shop.getPlatform());
|
||||||
|
item.setSelected(currentShop != null && shop.getShopId().equals(currentShop.getShopId()));
|
||||||
|
vo.getItems().add(item);
|
||||||
|
}
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZiniaoCurrentShopVo getCurrentShop(String sessionId) {
|
||||||
|
requireSession(sessionId);
|
||||||
|
ZiniaoShopCacheDto currentShop = getOrInitCurrentShop(sessionId, ziniaoSessionCacheService.getShops(sessionId));
|
||||||
|
if (currentShop == null) {
|
||||||
|
throw new BusinessException("当前会话没有可用店铺");
|
||||||
|
}
|
||||||
|
ZiniaoCurrentShopVo vo = new ZiniaoCurrentShopVo();
|
||||||
|
vo.setShopId(currentShop.getShopId());
|
||||||
|
vo.setShopName(currentShop.getShopName());
|
||||||
|
vo.setPlatform(currentShop.getPlatform());
|
||||||
|
vo.setSelectedAt(currentShop.getSelectedAt());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZiniaoSwitchShopVo switchShop(ZiniaoSwitchShopRequest request) {
|
||||||
|
requireSession(request.getSessionId());
|
||||||
|
List<ZiniaoShopCacheDto> shops = ziniaoSessionCacheService.getShops(request.getSessionId());
|
||||||
|
ZiniaoShopCacheDto targetShop = shops.stream()
|
||||||
|
.filter(item -> item.getShopId() != null && item.getShopId().equals(request.getShopId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new BusinessException("店铺不存在或不属于当前会话"));
|
||||||
|
targetShop.setSelectedAt(Instant.now().toEpochMilli());
|
||||||
|
ziniaoSessionCacheService.saveCurrentShop(request.getSessionId(), targetShop);
|
||||||
|
ZiniaoSwitchShopVo vo = new ZiniaoSwitchShopVo();
|
||||||
|
vo.setSuccess(true);
|
||||||
|
vo.setCurrentShopId(targetShop.getShopId());
|
||||||
|
vo.setCurrentShopName(targetShop.getShopName());
|
||||||
|
vo.setOpenStoreUrl(buildOpenStoreUrl(targetShop));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildFailureRedirect(String message) {
|
||||||
|
return ziniaoProperties.getBaseUrl() + "?message=" + (message == null || message.isBlank() ? "紫鸟授权失败" : message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String ensureSession() {
|
||||||
|
String sessionId = "ziniao_" + UUID.randomUUID().toString().replace("-", "");
|
||||||
|
ZiniaoSessionCacheDto session = new ZiniaoSessionCacheDto();
|
||||||
|
session.setSessionId(sessionId);
|
||||||
|
session.setAccessToken(maskApiKey(ziniaoProperties.getApiKey()));
|
||||||
|
session.setTokenType("Bearer");
|
||||||
|
session.setExpireAt(Instant.now().plusSeconds(ziniaoProperties.getSessionTtlHours() * 3600).toEpochMilli());
|
||||||
|
session.setZiniaoUserId(String.valueOf(defaultCompanyId()));
|
||||||
|
session.setNickname("ApiKey模式");
|
||||||
|
ziniaoSessionCacheService.saveSession(session);
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZiniaoSessionCacheDto requireSession(String sessionId) {
|
||||||
|
ensureEnabled();
|
||||||
|
if (sessionId == null || sessionId.isBlank()) {
|
||||||
|
throw new BusinessException("sessionId 不能为空");
|
||||||
|
}
|
||||||
|
ZiniaoSessionCacheDto session = ziniaoSessionCacheService.getSession(sessionId);
|
||||||
|
if (session == null) {
|
||||||
|
throw new BusinessException("当前会话不存在或已过期");
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZiniaoShopCacheDto getOrInitCurrentShop(String sessionId, List<ZiniaoShopCacheDto> shops) {
|
||||||
|
ZiniaoShopCacheDto current = ziniaoSessionCacheService.getCurrentShop(sessionId);
|
||||||
|
if (current != null) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
if (shops == null || shops.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ZiniaoShopCacheDto first = shops.get(0);
|
||||||
|
first.setSelectedAt(Instant.now().toEpochMilli());
|
||||||
|
ziniaoSessionCacheService.saveCurrentShop(sessionId, first);
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureEnabled() {
|
||||||
|
if (!ziniaoProperties.isEnabled()) {
|
||||||
|
throw new BusinessException("紫鸟集成未启用,请先配置环境变量");
|
||||||
|
}
|
||||||
|
if (ziniaoProperties.getApiKey() == null || ziniaoProperties.getApiKey().isBlank() || "change-me".equalsIgnoreCase(ziniaoProperties.getApiKey())) {
|
||||||
|
throw new BusinessException("紫鸟 ApiKey 未配置");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long defaultCompanyId() {
|
||||||
|
return ziniaoProperties.getCompanyId() == null ? 0L : ziniaoProperties.getCompanyId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String maskApiKey(String apiKey) {
|
||||||
|
if (apiKey == null || apiKey.isBlank()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (apiKey.length() <= 8) {
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
return apiKey.substring(0, 4) + "****" + apiKey.substring(apiKey.length() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildOpenStoreUrl(ZiniaoShopCacheDto shop) {
|
||||||
|
String scheme = requireText(ziniaoProperties.getOpenStoreScheme(), "紫鸟 open-store-scheme 未配置");
|
||||||
|
String userId = requireText(ziniaoProperties.getOpenStoreUserId(), "紫鸟 open-store-user-id 未配置");
|
||||||
|
String apiKey = requireText(ziniaoProperties.getApiKey(), "紫鸟 ApiKey 未配置");
|
||||||
|
String launchUrl = requireText(ziniaoProperties.getOpenStoreLaunchUrl(), "紫鸟 open-store-launch-url 未配置");
|
||||||
|
String encodedLaunchUrl = Base64.getEncoder().encodeToString(launchUrl.getBytes(StandardCharsets.UTF_8));
|
||||||
|
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(scheme)
|
||||||
|
.queryParam("storeId", shop.getShopId())
|
||||||
|
.queryParam("openapiToken", apiKey)
|
||||||
|
.queryParam("userId", userId)
|
||||||
|
.queryParam("lanuchUrl", encodedLaunchUrl)
|
||||||
|
.queryParam("autoopen", Boolean.TRUE.equals(ziniaoProperties.getOpenStoreAutoOpen()))
|
||||||
|
.queryParam("debuggPort", ziniaoProperties.getOpenStoreDebugPort())
|
||||||
|
.queryParam("notPromptForDownload", Boolean.TRUE.equals(ziniaoProperties.getOpenStoreNotPromptForDownload()) ? 1 : 0);
|
||||||
|
if (ziniaoProperties.getOpenStoreForceDownloadPath() != null && !ziniaoProperties.getOpenStoreForceDownloadPath().isBlank()) {
|
||||||
|
String encodedPath = Base64.getEncoder().encodeToString(ziniaoProperties.getOpenStoreForceDownloadPath().getBytes(StandardCharsets.UTF_8));
|
||||||
|
builder.queryParam("forceDownloadPath", encodedPath);
|
||||||
|
}
|
||||||
|
if (ziniaoProperties.getOpenStoreExtraArgs() != null && !ziniaoProperties.getOpenStoreExtraArgs().isBlank()) {
|
||||||
|
builder.queryParam("extraArgs", ziniaoProperties.getOpenStoreExtraArgs());
|
||||||
|
}
|
||||||
|
return builder.build(true).toUriString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireText(String value, String message) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
throw new BusinessException(message);
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.nanri.aiimage.common.exception.BusinessException;
|
||||||
|
import com.nanri.aiimage.config.ZiniaoProperties;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoSessionCacheDto;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopCacheDto;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ZiniaoSessionCacheService {
|
||||||
|
|
||||||
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final ZiniaoProperties ziniaoProperties;
|
||||||
|
|
||||||
|
public void saveSession(ZiniaoSessionCacheDto session) {
|
||||||
|
try {
|
||||||
|
stringRedisTemplate.opsForValue().set(buildSessionKey(session.getSessionId()), objectMapper.writeValueAsString(session), Duration.ofHours(ziniaoProperties.getSessionTtlHours()));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("保存紫鸟会话失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZiniaoSessionCacheDto getSession(String sessionId) {
|
||||||
|
String raw = stringRedisTemplate.opsForValue().get(buildSessionKey(sessionId));
|
||||||
|
if (raw == null || raw.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(raw, ZiniaoSessionCacheDto.class);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("读取紫鸟会话失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveShops(String sessionId, List<ZiniaoShopCacheDto> shops) {
|
||||||
|
try {
|
||||||
|
stringRedisTemplate.opsForValue().set(buildShopsKey(sessionId), objectMapper.writeValueAsString(shops), Duration.ofMinutes(ziniaoProperties.getShopsCacheMinutes()));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("保存紫鸟店铺列表失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ZiniaoShopCacheDto> getShops(String sessionId) {
|
||||||
|
String raw = stringRedisTemplate.opsForValue().get(buildShopsKey(sessionId));
|
||||||
|
if (raw == null || raw.isBlank()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(raw, new TypeReference<List<ZiniaoShopCacheDto>>() {});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("读取紫鸟店铺列表失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveCurrentShop(String sessionId, ZiniaoShopCacheDto shop) {
|
||||||
|
try {
|
||||||
|
stringRedisTemplate.opsForValue().set(buildCurrentShopKey(sessionId), objectMapper.writeValueAsString(shop), Duration.ofHours(ziniaoProperties.getSessionTtlHours()));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("保存当前紫鸟店铺失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZiniaoShopCacheDto getCurrentShop(String sessionId) {
|
||||||
|
String raw = stringRedisTemplate.opsForValue().get(buildCurrentShopKey(sessionId));
|
||||||
|
if (raw == null || raw.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(raw, ZiniaoShopCacheDto.class);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("读取当前紫鸟店铺失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSessionKey(String sessionId) {
|
||||||
|
return "ziniao:session:" + sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildShopsKey(String sessionId) {
|
||||||
|
return "ziniao:shops:" + sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildCurrentShopKey(String sessionId) {
|
||||||
|
return "ziniao:current-shop:" + sessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,3 +14,22 @@ AIIMAGE_OSS_ACCESS_KEY_ID=LTAI5tNpyvzMNz9f2dHarsm8
|
|||||||
AIIMAGE_OSS_ACCESS_KEY_SECRET=bQSZnFH455i8tzyOgeahJmUzwmhynz
|
AIIMAGE_OSS_ACCESS_KEY_SECRET=bQSZnFH455i8tzyOgeahJmUzwmhynz
|
||||||
|
|
||||||
AIIMAGE_STORAGE_LOCAL_TEMP_DIR=F:/project/crawler-plugin/backend-java/data/tmp
|
AIIMAGE_STORAGE_LOCAL_TEMP_DIR=F:/project/crawler-plugin/backend-java/data/tmp
|
||||||
|
|
||||||
|
AIIMAGE_ZINIAO_ENABLED=false
|
||||||
|
AIIMAGE_ZINIAO_BASE_URL=https://sbappstoreapi.ziniao.com/openapi-router
|
||||||
|
AIIMAGE_ZINIAO_API_KEY=change-me
|
||||||
|
AIIMAGE_ZINIAO_COMPANY_ID=0
|
||||||
|
AIIMAGE_ZINIAO_SHOPS_PATH=/superbrowser/rest/v1/erp/shop/list
|
||||||
|
AIIMAGE_ZINIAO_SWITCH_SHOP_PATH=
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_SCHEME=superbrowser://OpenStrore
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_USER_ID=
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_LAUNCH_URL=https://www.baidu.com
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_AUTO_OPEN=true
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_DEBUG_PORT=60001
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_NOT_PROMPT_FOR_DOWNLOAD=true
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_FORCE_DOWNLOAD_PATH=
|
||||||
|
AIIMAGE_ZINIAO_OPEN_STORE_EXTRA_ARGS=--disable-gpu start-maximized
|
||||||
|
AIIMAGE_ZINIAO_SESSION_TTL_HOURS=2
|
||||||
|
AIIMAGE_ZINIAO_SHOPS_CACHE_MINUTES=30
|
||||||
|
AIIMAGE_ZINIAO_CONNECT_TIMEOUT_SECONDS=5
|
||||||
|
AIIMAGE_ZINIAO_READ_TIMEOUT_SECONDS=15
|
||||||
|
|||||||
@@ -72,3 +72,22 @@ aiimage:
|
|||||||
failed-ttl-hours: ${AIIMAGE_BRAND_PROGRESS_FAILED_TTL_HOURS:2}
|
failed-ttl-hours: ${AIIMAGE_BRAND_PROGRESS_FAILED_TTL_HOURS:2}
|
||||||
heartbeat-timeout-minutes: ${AIIMAGE_BRAND_PROGRESS_HEARTBEAT_TIMEOUT_MINUTES:15}
|
heartbeat-timeout-minutes: ${AIIMAGE_BRAND_PROGRESS_HEARTBEAT_TIMEOUT_MINUTES:15}
|
||||||
stale-check-cron: ${AIIMAGE_BRAND_PROGRESS_STALE_CHECK_CRON:0 */2 * * * *}
|
stale-check-cron: ${AIIMAGE_BRAND_PROGRESS_STALE_CHECK_CRON:0 */2 * * * *}
|
||||||
|
ziniao:
|
||||||
|
enabled: ${AIIMAGE_ZINIAO_ENABLED:false}
|
||||||
|
base-url: ${AIIMAGE_ZINIAO_BASE_URL:https://sbappstoreapi.ziniao.com/openapi-router}
|
||||||
|
api-key: ${AIIMAGE_ZINIAO_API_KEY:change-me}
|
||||||
|
company-id: ${AIIMAGE_ZINIAO_COMPANY_ID:0}
|
||||||
|
shops-path: ${AIIMAGE_ZINIAO_SHOPS_PATH:/superbrowser/rest/v1/erp/shop/list}
|
||||||
|
switch-shop-path: ${AIIMAGE_ZINIAO_SWITCH_SHOP_PATH:}
|
||||||
|
open-store-scheme: ${AIIMAGE_ZINIAO_OPEN_STORE_SCHEME:superbrowser://OpenStrore}
|
||||||
|
open-store-user-id: ${AIIMAGE_ZINIAO_OPEN_STORE_USER_ID:}
|
||||||
|
open-store-launch-url: ${AIIMAGE_ZINIAO_OPEN_STORE_LAUNCH_URL:https://www.baidu.com}
|
||||||
|
open-store-auto-open: ${AIIMAGE_ZINIAO_OPEN_STORE_AUTO_OPEN:true}
|
||||||
|
open-store-debug-port: ${AIIMAGE_ZINIAO_OPEN_STORE_DEBUG_PORT:60001}
|
||||||
|
open-store-not-prompt-for-download: ${AIIMAGE_ZINIAO_OPEN_STORE_NOT_PROMPT_FOR_DOWNLOAD:true}
|
||||||
|
open-store-force-download-path: ${AIIMAGE_ZINIAO_OPEN_STORE_FORCE_DOWNLOAD_PATH:}
|
||||||
|
open-store-extra-args: ${AIIMAGE_ZINIAO_OPEN_STORE_EXTRA_ARGS:--disable-gpu start-maximized}
|
||||||
|
session-ttl-hours: ${AIIMAGE_ZINIAO_SESSION_TTL_HOURS:2}
|
||||||
|
shops-cache-minutes: ${AIIMAGE_ZINIAO_SHOPS_CACHE_MINUTES:30}
|
||||||
|
connect-timeout-seconds: ${AIIMAGE_ZINIAO_CONNECT_TIMEOUT_SECONDS:5}
|
||||||
|
read-timeout-seconds: ${AIIMAGE_ZINIAO_READ_TIMEOUT_SECONDS:15}
|
||||||
|
|||||||
Reference in New Issue
Block a user