- 获取
access_token:调用微信任何接口都需要这个凭证。 - 调用文件上传接口:使用
access_token和文件本身,向微信服务器提交文件。
第一步:准备工作
在开始编码之前,请确保你已经完成了以下准备工作:

-
获取 AppID 和 AppSecret:
- 登录 微信公众平台。
- 进入“开发” -> “基本配置”,找到你的 AppID(应用ID)和 AppSecret(应用密钥)。
- 注意:如果你是服务号或已认证的订阅号,才有权限调用上传多媒体文件的接口。
-
配置服务器 IP 白名单:
- 在“基本配置”页面,找到“开发者身份” -> “服务器配置”。
- 将你的 Java 后端服务器的公网 IP 地址添加到 IP 白名单中,否则,微信服务器无法回调你的接口,你也无法成功调用微信的接口。
第二步:核心代码实现
我们将使用流行的 Java HTTP 客户端库 OkHttp 来完成文件上传,如果你使用的是 Spring Boot,RestTemplate 也是一个不错的选择。
添加 Maven 依赖
在你的 pom.xml 文件中添加 OkHttp 的依赖:

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version> <!-- 建议使用较新版本 -->
</dependency>
定义微信 API 常量和工具类
为了代码清晰,我们定义一些常量和一个用于获取 access_token 的工具类。
WeChatApiConstants.java
public final class WeChatApiConstants {
// 替换成你的 AppID
public static final String APP_ID = "你的AppID";
// 替换成你的 AppSecret
public static final String APP_SECRET = "你的AppSecret";
// 获取 access_token 的接口
public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
// 上传多媒体文件的接口
public static final String MEDIA_UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s";
}
AccessTokenUtil.java
这是一个工具类,用于从微信服务器获取 access_token。access_token 有效期为 2 小时,建议你将其缓存起来(例如使用 Redis),并在过期后重新获取,而不是每次都请求微信。
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class AccessTokenUtil {
private static final OkHttpClient client = new OkHttpClient();
private static final ObjectMapper mapper = new ObjectMapper();
// 简单的内存缓存,生产环境请使用 Redis 等专业缓存
private static String cachedToken;
private static long tokenExpireTime = 0;
public static String getAccessToken() throws IOException {
// token 存在且未过期,直接返回
if (cachedToken != null && System.currentTimeMillis() < tokenExpireTime) {
return cachedToken;
}
// 否则,向微信服务器请求新的 token
String url = String.format(WeChatApiConstants.ACCESS_TOKEN_URL, WeChatApiConstants.APP_ID, WeChatApiConstants.APP_SECRET);
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
String responseBody = response.body().string();
JsonNode jsonNode = mapper.readTree(responseBody);
if (jsonNode.has("access_token")) {
// 微信返回的 access_token 有效期为 7200 秒(2小时),我们提前 5 分钟过期
long expiresIn = jsonNode.get("expires_in").asLong() - 300;
cachedToken = jsonNode.get("access_token").asText();
tokenExpireTime = System.currentTimeMillis() + expiresIn * 1000;
return cachedToken;
} else {
// 处理错误情况,例如获取 token 失败
String errcode = jsonNode.get("errcode").asText();
String errmsg = jsonNode.get("errmsg").asText();
throw new RuntimeException("获取微信 access_token 失败: " + errmsg + " (错误码: " + errcode + ")");
}
}
}
}
实现文件上传核心逻辑
我们来实现最关键的上传文件方法。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.io.File;
import java.io.IOException;
public class WeChatMediaUploader {
private static final OkHttpClient client = new OkHttpClient();
private static final ObjectMapper mapper = new ObjectMapper();
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
/**
* 上传文件到微信服务器
*
* @param filePath 本地文件路径
* @param mediaType 文件类型,可选: "image", "voice", "video", "thumb"
* @return 微信服务器返回的媒体ID (media_id)
* @throws IOException
*/
public static String uploadMedia(String filePath, String mediaType) throws IOException {
// 1. 获取 access_token
String accessToken = AccessTokenUtil.getAccessToken();
// 2. 构建上传 URL
String uploadUrl = String.format(WeChatApiConstants.MEDIA_UPLOAD_URL, accessToken, mediaType);
// 3. 创建文件请求体
File file = new File(filePath);
RequestBody fileBody = RequestBody.create(file, MEDIA_TYPE_PNG); // 注意:这里可以根据文件类型动态设置 MediaType
// 4. 构建请求
Request request = new Request.Builder()
.url(uploadUrl)
.post(fileBody)
.build();
// 5. 发送请求并处理响应
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response + " - " + response.body().string());
}
String responseBody = response.body().string();
System.out.println("微信服务器返回原始数据: " + responseBody);
JsonNode jsonNode = mapper.readTree(responseBody);
// 6. 解析返回结果
if (jsonNode.has("media_id")) {
return jsonNode.get("media_id").asText();
} else {
// 处理错误情况
String errcode = jsonNode.get("errcode").asText();
String errmsg = jsonNode.get("errmsg").asText();
throw new RuntimeException("上传文件到微信失败: " + errmsg + " (错误码: " + errcode + ")");
}
}
}
public static void main(String[] args) {
try {
// 示例:上传一张图片
// 请确保 "path/to/your/image.png" 这个路径是正确的
String mediaId = uploadMedia("path/to/your/image.png", "image");
System.out.println("上传成功!媒体ID为: " + mediaId);
// 你可以使用这个 media_id 进行后续操作,
// 1. 发送客服消息
// 2. 生成永久素材
// 3. 上传图文消息内的图片
} catch (IOException e) {
e.printStackTrace();
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
}
第三步:代码解读与注意事项
-
access_token缓存:AccessTokenUtil中做了一个简单的内存缓存。在生产环境中,强烈建议你使用 Redis 等分布式缓存,因为你的应用可能是多实例部署,内存缓存会导致不同实例的access_token不同步,并且应用重启后会失效。
-
文件类型 (
type):type参数必须是以下四种之一:image,voice,video,thumb。image: 图片(JPG, PNG, GIF)voice: 语音(AMR, AAC)video: 视频(MP4)thumb: 缩略图(用于视频消息的封面,建议尺寸为 160x160 的 JPG)
-
文件大小限制:
- 图片:1MB
- 语音:2MB,播放长度不超过 60 秒
- 视频:10MB
- 缩略图:64KB
- 超过大小限制的请求会失败。
-
MediaType:- 在
uploadMedia方法中,我使用了MediaType.parse("image/png"),这是一个硬编码的示例。 - 在实际应用中,你应该根据上传文件的真实 MIME 类型来动态设置
MediaType,对于.jpg文件,应使用image/jpeg,你可以使用Files.probeContentType()来探测文件类型。
- 在
-
错误处理:
- 微信接口在失败时会返回 JSON 格式的错误信息,包含
errcode和errmsg,代码中已经包含了基本的错误解析和抛出逻辑,你需要捕获这些异常并做相应处理(例如记录日志、返回错误信息给前端等)。
- 微信接口在失败时会返回 JSON 格式的错误信息,包含
-
返回结果:
- 上传成功后,微信服务器会返回一个 JSON 对象,其中包含
media_id、created_at等字段。media_id是后续操作(如发送消息)的关键,请务必妥善保存。
- 上传成功后,微信服务器会返回一个 JSON 对象,其中包含
第四步:进阶与常见问题
如何上传临时素材 vs 永久素材?
- 临时素材:我们上面实现的
cgi-bin/media/upload接口上传的就是临时素材,它会在 3 天后自动被微信服务器删除,适用于临时性的消息发送。 - 永久素材:如果你想长期保存这些文件(例如公众号文章的图片、视频),应该使用
cgi-bin/material/add_material接口,这个接口的调用方式与临时素材类似,但 URL 不同,并且可以上传更复杂的图文消息,返回的结果中会有一个media_id,这个media_id是永久的(除非你手动删除)。
如何在 Spring Boot 中实现?
如果你使用的是 Spring Boot,代码结构会更清晰。
-
创建一个 Service:
@Service public class WeChatService { @Value("${wechat.app-id}") private String appId; @Value("${wechat.app-secret}") private String appSecret; @Autowired private RedisTemplate<String, String> redisTemplate; public String getAccessToken() { String tokenKey = "wechat:access_token:" + appId; String token = redisTemplate.opsForValue().get(tokenKey); if (StringUtils.hasText(token)) { return token; } // ... 使用 RestTemplate 或 OkHttp 调用微信接口获取新 token ... // ... 获取后存入 Redis,并设置过期时间 ... // redisTemplate.opsForValue().set(tokenKey, newToken, 7000, TimeUnit.SECONDS); } public String uploadMedia(MultipartFile file, String mediaType) { String accessToken = getAccessToken(); // ... 使用 RestTemplate 的 MultiValueMap 来构建文件上传请求 ... // ... } } -
使用
RestTemplate上传文件:// 在 Controller 中接收前端传来的文件 @PostMapping("/upload") public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) { try { String mediaId = weChatService.uploadMedia(file, "image"); return ResponseEntity.ok("上传成功,media_id: " + mediaId); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传失败: " + e.getMessage()); } }
希望这份详细的指南能帮助你成功地在 Java 后端实现微信多媒体文件的上传功能!
