news 2026/4/5 16:06:27

Jimeng AI Studio(Z-Image Edition)SpringBoot安全集成:OAuth2认证实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jimeng AI Studio(Z-Image Edition)SpringBoot安全集成:OAuth2认证实现

Jimeng AI Studio(Z-Image Edition)SpringBoot安全集成:OAuth2认证实现

1. 为什么企业级AI应用必须重视认证安全

你有没有遇到过这样的情况:团队开发了一个内部AI图像生成服务,部署上线后发现任何人都能直接调用接口生成图片?或者更糟——有人把你的API密钥复制到外部网站,悄悄调用你的Z-Image模型资源?

这不是假设。在实际项目中,Jimeng AI Studio(Z-Image Edition)这类高性能图像生成能力一旦暴露在公网,很容易被滥用。轻则导致配额快速耗尽、账单飙升;重则引发数据泄露风险,甚至影响整个企业的AI基础设施稳定性。

OAuth2不是什么高深莫测的黑科技,它本质上是一套“授权委托”机制——就像你把家门钥匙交给信任的物业管家,而不是让所有人自由进出。在SpringBoot项目里集成OAuth2,就是为你的AI服务装上一把智能门锁:用户需要先通过身份验证,再获得有限权限的访问令牌,才能调用Z-Image的图像生成、编辑等核心能力。

这篇文章不讲抽象协议,也不堆砌RFC文档。我会带你从零开始,在一个真实的SpringBoot项目中,一步步完成Jimeng AI Studio(Z-Image Edition)的OAuth2安全集成。整个过程不需要你成为安全专家,只要你会写Controller、配置YAML、运行mvn命令就行。

部署完成后,你的应用将具备:

  • 用户登录后自动获取访问令牌
  • API调用时自动携带有效凭证
  • 令牌过期自动刷新机制
  • 错误响应清晰可读,便于前端处理

最关键的是,所有代码都经过实测,可以直接复制粘贴到你的项目中使用。

2. 环境准备与依赖配置

2.1 基础环境要求

在开始编码前,请确认你的开发环境满足以下最低要求:

  • JDK 17 或更高版本(推荐JDK 21)
  • Maven 3.8+
  • Spring Boot 3.2.x(本文基于3.2.12)
  • 一个可用的Jimeng AI Studio(Z-Image Edition)服务地址(本地或云环境)

注意:Jimeng AI Studio(Z-Image Edition)本身不提供OAuth2授权服务器。我们需要在SpringBoot应用中作为客户端接入其认证体系,或配合企业已有的身份平台(如Keycloak、Auth0)使用。本文以标准OAuth2客户端模式为主,适配主流身份提供商。

2.2 添加核心依赖

打开pom.xml,在<dependencies>节点中添加以下内容:

<!-- Spring Security OAuth2 Client --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <!-- Spring Security Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Thymeleaf模板引擎(用于登录页) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Lombok(简化实体类) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

如果你使用Gradle,对应添加:

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'

2.3 配置OAuth2客户端参数

application.yml中添加OAuth2相关配置。这里我们以通用结构为例,实际需根据你对接的身份提供商调整:

spring: security: oauth2: client: registration: jimeng: client-id: your-client-id-here client-secret: your-client-secret-here scope: openid,profile,email,api:zimage:generate,api:zimage:edit provider: jimeng: authorization-uri: https://auth.jimeng.ai/oauth2/authorize token-uri: https://auth.jimeng.ai/oauth2/token user-info-uri: https://auth.jimeng.ai/oauth2/userinfo jwk-set-uri: https://auth.jimeng.ai/oauth2/jwks user-name-attribute: sub # 可选:自定义登录成功后的跳转路径 mvc: throw-exception-if-no-handler-found: true web: resources: add-mappings: false

说明

  • client-idclient-secret需向Jimeng AI Studio管理后台申请获取
  • scopeapi:zimage:generate表示图像生成权限,api:zimage:edit表示图像编辑权限,可根据业务按需申请
  • jwk-set-uri用于验证JWT签名,确保令牌真实可信

如果你使用的是企业内网部署的Keycloak,provider配置会略有不同,但整体结构一致。

3. 安全配置与认证流程实现

3.1 自定义OAuth2安全配置类

创建src/main/java/com/example/config/SecurityConfig.java

package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.util.*; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/", "/login", "/webjars/**", "/css/**", "/js/**").permitAll() .requestMatchers("/api/public/**").permitAll() .requestMatchers("/api/zimage/**").authenticated() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .loginPage("/login") .defaultSuccessUrl("/dashboard", true) .failureUrl("/login?error=true") .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService()) ) ) .logout(logout -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/") .invalidateHttpSession(true) .deleteCookies("JSESSIONID") ); return http.build(); } @Bean public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() { return new CustomOAuth2UserService(); } }

这个配置做了几件关键的事:

  • 允许未登录用户访问首页、登录页、静态资源
  • 要求所有/api/zimage/**接口必须登录后才能访问
  • 自定义登录成功后跳转到/dashboard
  • 指定错误跳转路径,便于前端友好提示
  • 配置登出逻辑,清除会话和Cookie

3.2 实现用户信息映射逻辑

创建src/main/java/com/example/config/CustomOAuth2UserService.java

package com.example.config; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.*; @Service public class CustomOAuth2UserService extends DefaultOAuth2UserService { @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); // 获取原始属性 Map<String, Object> attributes = new HashMap<>(oAuth2User.getAttributes()); // 提取用户唯一标识(通常为sub或email) String userId = Optional.ofNullable(attributes.get("sub")) .map(Object::toString) .orElseGet(() -> Optional.ofNullable(attributes.get("email")) .map(Object::toString) .orElse("anonymous")); // 构建用户角色(可根据token中roles字段动态解析) Set<GrantedAuthority> authorities = new HashSet<>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // 如果token中包含zimage权限,可额外添加 if (attributes.containsKey("scope")) { String scope = attributes.get("scope").toString(); if (scope.contains("api:zimage:generate")) { authorities.add(new SimpleGrantedAuthority("SCOPE_zimage_generate")); } if (scope.contains("api:zimage:edit")) { authorities.add(new SimpleGrantedAuthority("SCOPE_zimage_edit")); } } // 返回增强后的OAuth2User对象 return new DefaultOAuth2User( authorities, attributes, "sub" // 主键字段名 ); } }

这段代码的作用是:当用户完成OAuth2登录后,从返回的JWT中提取关键信息(如用户ID、权限范围),并将其转换为Spring Security可识别的OAuth2User对象。这样后续Controller就能通过@AuthenticationPrincipal轻松获取当前用户信息。

3.3 创建登录与仪表盘页面

src/main/resources/templates/下创建login.html

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Jimeng AI Studio 登录</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body class="bg-light"> <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow-sm"> <div class="card-header bg-primary text-white"> <h4 class="mb-0">欢迎使用 Jimeng AI Studio(Z-Image Edition)</h4> </div> <div class="card-body"> <p class="lead">请使用企业统一身份平台登录</p> <!-- OAuth2登录按钮 --> <a href="/oauth2/authorization/jimeng" class="btn btn-lg btn-success w-100 mb-3"> <i class="bi bi-journal-code"></i> 使用 Jimeng 账户登录 </a> <!-- 错误提示 --> <div th:if="${param.error}" class="alert alert-danger"> 登录失败,请检查网络或联系管理员 </div> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html>

再创建dashboard.html作为登录成功后的首页:

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>AI图像工作台</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-4"> <nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm mb-4"> <div class="container-fluid"> <a class="navbar-brand fw-bold" href="#"> <span class="text-primary">Jimeng</span> AI Studio </a> <div class="d-flex align-items-center"> <span class="me-3 text-muted" th:text="${#authentication.principal.attributes['name']} ?: '未知用户'"></span> <a href="/logout" class="btn btn-outline-danger btn-sm">退出登录</a> </div> </div> </nav> <div class="row"> <div class="col-md-8"> <div class="card mb-4"> <div class="card-header bg-success text-white"> <h5 class="mb-0">Z-Image 图像生成服务</h5> </div> <div class="card-body"> <p>已成功接入 Jimeng AI Studio(Z-Image Edition),当前用户拥有图像生成权限。</p> <button class="btn btn-primary" onclick="generateImage()">立即生成一张测试图</button> <div id="result" class="mt-3"></div> </div> </div> </div> <div class="col-md-4"> <div class="card"> <div class="card-header bg-info text-white"> <h5 class="mb-0">权限说明</h5> </div> <div class="card-body"> <ul class="list-unstyled"> <li class="mb-2"><strong> 已授权:</strong>图像生成</li> <li class="mb-2"><strong> 已授权:</strong>图像编辑</li> <li class="mb-2"><strong> 待申请:</strong>批量处理(需管理员审批)</li> </ul> </div> </div> </div> </div> </div> <script> function generateImage() { fetch('/api/zimage/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: "一只戴着墨镜的金毛犬在沙滩上,日落光线,专业摄影", size: "1024x1024" }) }) .then(response => response.json()) .then(data => { document.getElementById('result').innerHTML = ` <div class="alert alert-success"> <strong> 生成成功!</strong><br> 图片URL:<a href="${data.imageUrl}" target="_blank">${data.imageUrl}</a> </div> `; }) .catch(error => { document.getElementById('result').innerHTML = ` <div class="alert alert-danger"> <strong> 生成失败:</strong>${error.message} </div> `; }); } </script> </body> </html>

这些页面采用简洁的Bootstrap样式,重点突出OAuth2登录入口,并在仪表盘中展示当前用户的权限状态,让用户一目了然。

4. Z-Image API调用与令牌管理

4.1 创建Z-Image客户端服务

创建src/main/java/com/example/service/ZImageClientService.java

package com.example.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.Collections; @Slf4j @Service @RequiredArgsConstructor public class ZImageClientService { private final RestTemplate restTemplate; private final ObjectMapper objectMapper; private final OAuth2AuthorizedClientService authorizedClientService; @Value("${zimage.api.base-url}") private String zImageBaseUrl; /** * 调用Z-Image图像生成API */ public JsonNode generateImage(Authentication authentication, String prompt, String size) { // 获取当前用户的OAuth2授权客户端 OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication; String clientRegistrationId = oauth2Token.getAuthorizedClientRegistrationId(); OAuth2AuthorizedClient authorizedClient = authorizedClientService .loadAuthorizedClient(clientRegistrationId, oauth2Token.getName()); if (authorizedClient == null) { throw new RuntimeException("用户未完成OAuth2授权流程"); } // 构建请求头,携带访问令牌 HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(authorizedClient.getAccessToken().getTokenValue()); headers.setContentType(MediaType.APPLICATION_JSON); // 构建请求体 String requestBody = String.format( "{\"prompt\":\"%s\",\"size\":\"%s\",\"model\":\"z-image-turbo\"}", prompt.replace("\"", "\\\""), size ); HttpEntity<String> request = new HttpEntity<>(requestBody, headers); try { ResponseEntity<String> response = restTemplate.postForEntity( zImageBaseUrl + "/v1/images/generations", request, String.class ); if (response.getStatusCode().is2xxSuccessful()) { JsonNode rootNode = objectMapper.readTree(response.getBody()); log.info("Z-Image生成成功,请求ID:{}", rootNode.path("id").asText()); return rootNode; } else { throw new RuntimeException("Z-Image API调用失败:" + response.getStatusCode()); } } catch (Exception e) { log.error("调用Z-Image API异常", e); throw new RuntimeException("图像生成服务暂时不可用", e); } } }

这个服务类封装了对Z-Image后端API的安全调用逻辑。关键点在于:

  • 从Spring Security上下文中获取当前用户的OAuth2AuthorizedClient
  • 自动提取有效的访问令牌(access_token)
  • 在HTTP请求头中设置Authorization: Bearer <token>
  • 处理常见错误场景(如令牌过期、网络异常)

4.2 实现REST控制器

创建src/main/java/com/example/controller/ZImageController.java

package com.example.controller; import com.example.service.ZImageClientService; import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/zimage") @RequiredArgsConstructor public class ZImageController { private final ZImageClientService zImageClientService; @PostMapping("/generate") public ResponseEntity<Map<String, Object>> generateImage( Authentication authentication, @RequestBody Map<String, String> request) { String prompt = request.getOrDefault("prompt", "一只猫在窗台上晒太阳"); String size = request.getOrDefault("size", "1024x1024"); try { JsonNode result = zImageClientService.generateImage(authentication, prompt, size); Map<String, Object> response = new HashMap<>(); response.put("success", true); response.put("imageUrl", result.path("data").path(0).path("url").asText()); response.put("requestId", result.path("id").asText()); response.put("prompt", prompt); return ResponseEntity.ok(response); } catch (Exception e) { Map<String, Object> errorResponse = new HashMap<>(); errorResponse.put("success", false); errorResponse.put("error", e.getMessage()); return ResponseEntity.status(500).body(errorResponse); } } @GetMapping("/health") public ResponseEntity<Map<String, Object>> healthCheck() { Map<String, Object> status = new HashMap<>(); status.put("status", "UP"); status.put("service", "zimage-integration"); status.put("timestamp", System.currentTimeMillis()); return ResponseEntity.ok(status); } }

该Controller提供了两个端点:

  • POST /api/zimage/generate:接收前端传来的提示词,调用Z-Image生成图像
  • GET /api/zimage/health:健康检查接口,便于运维监控

注意:所有/api/zimage/**路径都受到Spring Security保护,未登录用户无法访问。

4.3 处理令牌刷新与失效场景

OAuth2令牌通常有较短的有效期(如1小时)。为了提升用户体验,我们需要在令牌即将过期时自动刷新。Spring Security OAuth2 Client默认支持此功能,但需确保配置正确。

application.yml中补充以下配置:

spring: security: oauth2: client: provider: jimeng: # ... 其他配置保持不变 registration: jimeng: # ... 其他配置保持不变 # 启用OAuth2令牌自动刷新 security: oauth2: resourceserver: jwt: jwk-set-uri: https://auth.jimeng.ai/oauth2/jwks

同时,在SecurityConfig.java中启用OAuth2AuthorizedClientService的自动刷新能力:

@Bean public OAuth2AuthorizedClientService authorizedClientService( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); }

Spring Security会在检测到令牌过期时,自动使用refresh_token发起刷新请求,无需你在业务代码中手动处理。

5. 实际效果与调试建议

5.1 运行效果演示

启动应用后,访问http://localhost:8080,你会看到一个干净的登录页面。点击“使用 Jimeng 账户登录”,将跳转至Jimeng AI Studio的OAuth2授权页面(或你配置的身份提供商页面)。用户输入账号密码并授权后,自动重定向回/dashboard,显示仪表盘界面。

此时浏览器开发者工具的Network面板中,你可以观察到:

  • /oauth2/authorization/jimeng→ 触发授权码流程
  • /login/oauth2/code/jimeng→ 接收授权码并交换令牌
  • /api/zimage/generate→ 请求头中已包含Authorization: Bearer eyJhb...

生成的图片URL可以直接在新标签页中打开,查看Z-Image生成的实际效果。你会发现,无论是写实风格的宠物照,还是插画风的科幻场景,都能在几秒内完成高质量输出。

5.2 常见问题排查指南

在实际集成过程中,你可能会遇到以下典型问题,这里提供快速定位方法:

问题1:登录后跳转到空白页或404

  • 检查application.ymlspring.security.oauth2.client.registration.jimeng.client-id是否填写正确
  • 确认Jimeng后台配置的Redirect URI是否为http://localhost:8080/login/oauth2/code/jimeng
  • 查看控制台日志是否有Invalid redirect_uri报错

问题2:调用Z-Image API返回401 Unauthorized

  • 使用Postman模拟请求,手动传入Authorization: Bearer <token>,确认令牌本身是否有效
  • 检查ZImageClientService中是否正确获取了authorizedClient.getAccessToken()
  • 查看令牌是否已过期(.getExpiresAt()时间戳)

问题3:用户信息无法正确映射

  • CustomOAuth2UserService.loadUser()方法中添加日志,打印oAuth2User.getAttributes()
  • 确认user-name-attribute配置是否匹配JWT中的主键字段(如subemail等)

问题4:中文提示词生成效果不佳

  • 这属于Z-Image模型本身的优化范畴,与OAuth2集成无关
  • 建议在提示词前添加语言标识,如[中文]一只戴着墨镜的金毛犬...
  • 或使用Z-Image Turbo模型,对多语言支持更优

5.3 生产环境加固建议

当你准备将这套方案部署到生产环境时,还需考虑以下几点:

  • HTTPS强制:在Nginx或网关层配置HTTP→HTTPS重定向,确保所有通信加密
  • 令牌存储:避免将access_token存入前端localStorage,建议由后端统一管理会话
  • 权限细化:根据RBAC模型,为不同角色分配不同的Z-Image操作权限(如设计师可编辑,运营仅可生成)
  • 审计日志:记录每次Z-Image API调用的用户、时间、提示词、结果状态,便于事后追溯
  • 熔断降级:集成Resilience4j,在Z-Image服务不可用时返回缓存图像或友好提示

这些不是OAuth2集成的必需步骤,但在企业级应用中至关重要。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/30 5:34:31

AcousticSense AI保姆级教程:inference.py中confidence threshold动态调节

AcousticSense AI保姆级教程&#xff1a;inference.py中confidence threshold动态调节 1. 为什么需要动态调节置信度阈值&#xff1f; 你有没有遇到过这样的情况&#xff1a;上传一首爵士乐&#xff0c;模型却给出了“古典”和“蓝调”两个高分结果&#xff0c;而实际流派只有…

作者头像 李华
网站建设 2026/3/29 4:59:53

bge-large-zh-v1.5从零开始:无需CUDA手动编译的镜像免配置部署

bge-large-zh-v1.5从零开始&#xff1a;无需CUDA手动编译的镜像免配置部署 你是不是也遇到过这样的问题&#xff1a;想快速用上中文效果最好的embedding模型之一bge-large-zh-v1.5&#xff0c;却发现环境配置卡在CUDA版本、PyTorch编译、依赖冲突上&#xff1f;显卡驱动没对上…

作者头像 李华
网站建设 2026/3/24 13:09:15

MT5中文增强工具开发者手册:自定义模型路径、扩展输出格式方法

MT5中文增强工具开发者手册&#xff1a;自定义模型路径、扩展输出格式方法 1. 工具定位与核心价值 你是否遇到过这样的问题&#xff1a;手头只有几十条中文样本&#xff0c;却要训练一个分类模型&#xff1f;或者写好的产品文案总显得单薄&#xff0c;想快速生成多个表达版本…

作者头像 李华
网站建设 2026/3/31 19:36:11

KOOK真实幻想艺术馆保姆级教程:中文提示词质量提升5大技巧

KOOK真实幻想艺术馆保姆级教程&#xff1a;中文提示词质量提升5大技巧 1. 前言&#xff1a;为什么提示词如此重要 在AI艺术创作领域&#xff0c;提示词就像画家的调色盘和画笔。KOOK真实幻想艺术馆&#xff08;Starry Night Art Gallery&#xff09;虽然提供了强大的自动翻译…

作者头像 李华