第一次提交
This commit is contained in:
commit
f95e085e7b
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
1
.idea/.name
Normal file
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
||||
mp_chajian
|
||||
7
.idea/inspectionProfiles/Project_Default.xml
Normal file
7
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10 (text translation)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/test.iml" filepath="$PROJECT_DIR$/.idea/test.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/mp_chajian.iml
Normal file
8
.idea/mp_chajian.iml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$/../mp_chajian" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
72
README.md
Normal file
72
README.md
Normal file
@ -0,0 +1,72 @@
|
||||
# 微信公众号插件API
|
||||
|
||||
这是一个基于FastAPI构建的微信公众号插件API服务,提供以下功能:
|
||||
|
||||
1. 获取微信公众号access_token
|
||||
2. 上传临时图片到微信公众号素材库
|
||||
3. 上传永久图片到微信公众号素材库
|
||||
4. 上传文章草稿到微信公众号
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 启动服务
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
服务将在 `http://localhost:8000` 上运行。
|
||||
|
||||
## API文档
|
||||
|
||||
启动服务后,可以通过以下地址访问API文档:
|
||||
|
||||
- Swagger UI: http://localhost:8000/docs
|
||||
- ReDoc: http://localhost:8000/redoc
|
||||
|
||||
## API接口说明
|
||||
|
||||
### 1. 获取access_token
|
||||
|
||||
- **URL**: `/token`
|
||||
- **方法**: POST
|
||||
- **参数**:
|
||||
- `appid` (string): 公众号的APPID
|
||||
- `appsecret` (string): 公众号的AppSecret
|
||||
- **返回**: access_token
|
||||
|
||||
### 2. 上传临时图片
|
||||
|
||||
- **URL**: `/upload_temp_image`
|
||||
- **方法**: POST
|
||||
- **参数**:
|
||||
- `access_token` (string): 公众号的access_token
|
||||
- `image_url` (string): 图片的URL地址
|
||||
- **返回**: media_id
|
||||
|
||||
### 3. 上传永久图片
|
||||
|
||||
- **URL**: `/upload_permanent_image`
|
||||
- **方法**: POST
|
||||
- **参数**:
|
||||
- `access_token` (string): 公众号的access_token
|
||||
- `image_url` (string): 图片的URL地址
|
||||
- **返回**: 永久素材的media_id
|
||||
|
||||
### 4. 上传文章草稿
|
||||
|
||||
- **URL**: `/upload_draft`
|
||||
- **方法**: POST
|
||||
- **参数**:
|
||||
- `access_token` (string): 公众号的access_token
|
||||
- `title` (string): 文章标题
|
||||
- `content` (string): 文章内容(HTML格式)
|
||||
- `cover_media_id` (string): 封面图片的media_id
|
||||
- `author` (string): 作者
|
||||
- `digest` (string, optional): 文章摘要
|
||||
- `show_cover_pic` (int, optional): 是否显示封面,0不显示,1显示,默认为1
|
||||
- **返回**: 草稿的media_id
|
||||
57
get_media_id.py
Normal file
57
get_media_id.py
Normal file
@ -0,0 +1,57 @@
|
||||
import requests
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def upload_image_to_wechat(access_token, image_url):
|
||||
"""
|
||||
上传图片到微信公众号素材库
|
||||
|
||||
参数:
|
||||
access_token (str): 公众号的access_token
|
||||
image_url (str): 图片的URL地址
|
||||
|
||||
返回:
|
||||
str: 成功时返回media_id
|
||||
None: 失败时返回None
|
||||
"""
|
||||
if not access_token or not image_url:
|
||||
return None
|
||||
|
||||
# 微信上传临时素材接口
|
||||
upload_url = f"https://api.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type=image"
|
||||
|
||||
try:
|
||||
# 先从URL下载图片
|
||||
image_response = requests.get(image_url, timeout=15)
|
||||
image_response.raise_for_status() # 检查请求是否成功
|
||||
|
||||
# 将图片内容转换为文件对象
|
||||
image_file = BytesIO(image_response.content)
|
||||
|
||||
# 构造表单数据
|
||||
files = {
|
||||
'media': ('image.jpg', image_file, 'image/jpeg')
|
||||
}
|
||||
|
||||
# 上传图片到微信服务器
|
||||
response = requests.post(upload_url, files=files, timeout=15)
|
||||
result = json.loads(response.text)
|
||||
|
||||
# 检查是否返回错误
|
||||
if "errcode" in result and result["errcode"] != 0:
|
||||
return None
|
||||
|
||||
# 返回media_id
|
||||
media_id = result.get("media_id")
|
||||
if media_id:
|
||||
return media_id
|
||||
else:
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
62
get_permanent_media.py
Normal file
62
get_permanent_media.py
Normal file
@ -0,0 +1,62 @@
|
||||
import requests
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def upload_permanent_image_to_wechat(access_token, image_url):
|
||||
"""
|
||||
上传永久图片到微信公众号素材库
|
||||
|
||||
参数:
|
||||
access_token (str): 公众号的access_token
|
||||
image_url (str): 图片的URL地址
|
||||
|
||||
返回:
|
||||
str: 成功时返回永久素材的media_id
|
||||
None: 失败时返回None
|
||||
"""
|
||||
if not access_token or not image_url:
|
||||
return None
|
||||
|
||||
# 微信上传永久素材接口(图片类型)
|
||||
upload_url = f"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={access_token}&type=image"
|
||||
|
||||
try:
|
||||
# 先从URL下载图片
|
||||
image_response = requests.get(image_url, timeout=15)
|
||||
image_response.raise_for_status() # 检查请求是否成功
|
||||
|
||||
# 检查图片大小(微信限制永久图片不超过2MB)
|
||||
image_size = len(image_response.content)
|
||||
if image_size > 2 * 1024 * 1024: # 2MB
|
||||
return None
|
||||
|
||||
# 将图片内容转换为文件对象
|
||||
image_file = BytesIO(image_response.content)
|
||||
|
||||
# 构造表单数据
|
||||
files = {
|
||||
'media': ('image.jpg', image_file, 'image/jpeg')
|
||||
}
|
||||
|
||||
# 上传图片到微信服务器
|
||||
response = requests.post(upload_url, files=files, timeout=15)
|
||||
result = json.loads(response.text)
|
||||
|
||||
# 检查是否返回错误
|
||||
if "errcode" in result and result["errcode"] != 0:
|
||||
return None
|
||||
|
||||
# 返回media_id
|
||||
media_id = result.get("media_id")
|
||||
if media_id:
|
||||
return media_id
|
||||
else:
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
39
get_token.py
Normal file
39
get_token.py
Normal file
@ -0,0 +1,39 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
def get_wechat_token(appid, appsecret):
|
||||
"""
|
||||
通过微信公众号的APPID和AppSecret获取access_token
|
||||
|
||||
参数:
|
||||
appid (str): 公众号的APPID
|
||||
appsecret (str): 公众号的AppSecret
|
||||
|
||||
返回:
|
||||
str: 成功时返回access_token
|
||||
None: 失败时返回None
|
||||
"""
|
||||
if not appid or not appsecret:
|
||||
return None
|
||||
|
||||
# 微信获取token的接口
|
||||
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={appsecret}"
|
||||
|
||||
try:
|
||||
# 发送GET请求
|
||||
response = requests.get(url, timeout=10)
|
||||
# 解析JSON响应
|
||||
result = json.loads(response.text)
|
||||
|
||||
# 检查是否返回了错误信息
|
||||
if "errcode" in result and result["errcode"] != 0:
|
||||
return None
|
||||
|
||||
# 返回获取到的token
|
||||
return result.get("access_token")
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
113
main.py
Normal file
113
main.py
Normal file
@ -0,0 +1,113 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
import uvicorn
|
||||
|
||||
from get_token import get_wechat_token
|
||||
from get_media_id import upload_image_to_wechat
|
||||
from get_permanent_media import upload_permanent_image_to_wechat
|
||||
from upload_draft import upload_article_draft
|
||||
|
||||
app = FastAPI(
|
||||
title="微信公众号插件API",
|
||||
description="提供微信公众号相关功能的API接口",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# 定义请求模型
|
||||
class TokenRequest(BaseModel):
|
||||
appid: str
|
||||
appsecret: str
|
||||
|
||||
class ImageUploadRequest(BaseModel):
|
||||
access_token: str
|
||||
image_url: str
|
||||
|
||||
class DraftUploadRequest(BaseModel):
|
||||
access_token: str
|
||||
title: str
|
||||
content: str
|
||||
cover_media_id: str
|
||||
author: str
|
||||
digest: Optional[str] = None
|
||||
show_cover_pic: Optional[int] = 1
|
||||
|
||||
# 定义响应模型
|
||||
class TokenResponse(BaseModel):
|
||||
success: bool
|
||||
token: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
class MediaResponse(BaseModel):
|
||||
success: bool
|
||||
media_id: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
class DraftResponse(BaseModel):
|
||||
success: bool
|
||||
media_id: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "微信公众号插件API服务已启动"}
|
||||
|
||||
@app.post("/token", response_model=TokenResponse)
|
||||
async def get_token(request: TokenRequest):
|
||||
"""获取微信公众号access_token"""
|
||||
try:
|
||||
token = get_wechat_token(request.appid, request.appsecret)
|
||||
if token:
|
||||
return TokenResponse(success=True, token=token)
|
||||
else:
|
||||
return TokenResponse(success=False, error="获取token失败")
|
||||
except Exception as e:
|
||||
return TokenResponse(success=False, error=str(e))
|
||||
|
||||
@app.post("/upload_temp_image", response_model=MediaResponse)
|
||||
async def upload_temp_image(request: ImageUploadRequest):
|
||||
"""上传临时图片到微信公众号素材库"""
|
||||
try:
|
||||
media_id = upload_image_to_wechat(request.access_token, request.image_url)
|
||||
if media_id:
|
||||
return MediaResponse(success=True, media_id=media_id)
|
||||
else:
|
||||
return MediaResponse(success=False, error="上传临时图片失败")
|
||||
except Exception as e:
|
||||
return MediaResponse(success=False, error=str(e))
|
||||
|
||||
@app.post("/upload_permanent_image", response_model=MediaResponse)
|
||||
async def upload_permanent_image(request: ImageUploadRequest):
|
||||
"""上传永久图片到微信公众号素材库"""
|
||||
try:
|
||||
media_id = upload_permanent_image_to_wechat(request.access_token, request.image_url)
|
||||
if media_id:
|
||||
return MediaResponse(success=True, media_id=media_id)
|
||||
else:
|
||||
return MediaResponse(success=False, error="上传永久图片失败")
|
||||
except Exception as e:
|
||||
return MediaResponse(success=False, error=str(e))
|
||||
|
||||
@app.post("/upload_draft", response_model=DraftResponse)
|
||||
async def upload_draft(request: DraftUploadRequest):
|
||||
"""上传文章草稿到微信公众号"""
|
||||
try:
|
||||
media_id = upload_article_draft(
|
||||
request.access_token,
|
||||
request.title,
|
||||
request.content,
|
||||
request.cover_media_id,
|
||||
request.author,
|
||||
request.digest,
|
||||
request.show_cover_pic
|
||||
)
|
||||
if media_id:
|
||||
return DraftResponse(success=True, media_id=media_id)
|
||||
else:
|
||||
return DraftResponse(success=False, error="上传文章草稿失败")
|
||||
except Exception as e:
|
||||
return DraftResponse(success=False, error=str(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
fastapi>=0.68.0
|
||||
uvicorn>=0.15.0
|
||||
requests>=2.25.1
|
||||
pydantic>=1.8.0
|
||||
75
upload_draft.py
Normal file
75
upload_draft.py
Normal file
@ -0,0 +1,75 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
def upload_article_draft(access_token, title, content, cover_media_id, author, digest=None, show_cover_pic=1):
|
||||
"""
|
||||
上传文章草稿到微信公众号草稿箱
|
||||
|
||||
参数:
|
||||
access_token (str): 公众号的access_token
|
||||
title (str): 文章标题
|
||||
content (str): 文章内容(HTML格式)
|
||||
cover_media_id (str): 封面图片的media_id
|
||||
author (str): 作者
|
||||
digest (str, optional): 文章摘要,默认为None
|
||||
show_cover_pic (int, optional): 是否显示封面,0不显示,1显示,默认为1
|
||||
|
||||
返回:
|
||||
str: 成功时返回草稿的media_id
|
||||
None: 失败时返回None
|
||||
"""
|
||||
# 验证必填参数
|
||||
if not all([access_token, title, content, cover_media_id, author]):
|
||||
return None
|
||||
|
||||
# 微信上传草稿接口
|
||||
url = f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={access_token}"
|
||||
|
||||
# 构造请求数据
|
||||
article_data = {
|
||||
"articles": [
|
||||
{
|
||||
"title": title,
|
||||
"content": content,
|
||||
"thumb_media_id": cover_media_id,
|
||||
"author": author,
|
||||
"show_cover_pic": show_cover_pic
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# 添加可选参数
|
||||
if digest:
|
||||
article_data["articles"][0]["digest"] = digest
|
||||
|
||||
try:
|
||||
# 发送POST请求
|
||||
response = requests.post(
|
||||
url,
|
||||
data=json.dumps(article_data, ensure_ascii=False).encode('utf-8'),
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=15
|
||||
)
|
||||
|
||||
# 解析响应结果
|
||||
result = json.loads(response.text)
|
||||
|
||||
# 检查是否有错误
|
||||
if "errcode" in result and result["errcode"] != 0:
|
||||
return None
|
||||
|
||||
# 返回草稿的media_id
|
||||
media_id = result.get("media_id")
|
||||
if media_id:
|
||||
return media_id
|
||||
else:
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
Loading…
Reference in New Issue
Block a user