第一次提交
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