给桌面软件做一个插件,把某个图片上传到极态云的图片字段。
1、我发现极态云图片字段是有特殊的存储结构,
2、我发现在网页端操作的时候,oss的存储会在jit_storage表中有一条记录,然后在图片字段中存的好像是这个表的一条json值
3、如果我想要实现本地图片上传极态云的图片字段中,是否一定要在这个表中创建一条记录?是否一定要构造成某个格式,前端才能正常展示?
给桌面软件做一个插件,把某个图片上传到极态云的图片字段。
1、我发现极态云图片字段是有特殊的存储结构,
2、我发现在网页端操作的时候,oss的存储会在jit_storage表中有一条记录,然后在图片字段中存的好像是这个表的一条json值
3、如果我想要实现本地图片上传极态云的图片字段中,是否一定要在这个表中创建一条记录?是否一定要构造成某个格式,前端才能正常展示?
可以做,但不建议桌面软件直接操作 OSS、jit_storage 或业务模型表。推荐做法是:在极态云应用里封装一个后端服务函数,桌面软件只调用这个服务函数上传图片;服务函数负责上传文件、生成图片字段需要的 JSON,再更新模型字段。
图片字段最终保存的不是图片二进制,也不是单独一个 URL,而是一个图片对象数组,例如:
[
{
"uid": "唯一值",
"name": "存储后的文件名或对象名",
"fileName": "原始文件名.png",
"url": "https://...",
"md5": "...",
"size": 12345,
"type": "image/png"
}
]
前端正常展示主要依赖这个数组结构,尤其是 url、fileName、uid、size、type 这些字段。只存一个 URL,或者字段值不是数组,后续展示、预览、编辑都容易出问题。
jit_storage 对应的是平台文件记录。图片字段本身不要求你直接 SQL 插入 jit_storage,但如果希望和网页端上传后的文件管理、使用记录保持一致,应该通过平台接口或模型 API 创建这条文件记录,而不是手工写库。
可以在应用里建一个服务函数:
services/DesktopUploadSvc/uploadImageToModelField
桌面端请求地址通常是:
POST https://你的域名/api/<组织ID>/<应用ID>/services/DesktopUploadSvc/uploadImageToModelField
例如应用的 apiPath 是 /api/whwy/demoapp,那完整地址就是:
https://你的域名/api/whwy/demoapp/services/DesktopUploadSvc/uploadImageToModelField
请求方式用 multipart/form-data:
curl -X POST \
-H "Authorization: Bearer <your-token>" \
-F "file=@/path/to/a.png;type=image/png" \
-F "modelFullName=xxx.models.Customer" \
-F "pk=123" \
-F "fieldName=avatar" \
"https://你的域名/api/<组织ID>/<应用ID>/services/DesktopUploadSvc/uploadImageToModelField"
参数含义:
file: 本地图片文件
modelFullName: 要更新的模型 fullName,例如 xxx.models.Customer
pk: 要更新的记录主键
fieldName: 图片字段名
示例逻辑是“上传一张图片并覆盖写入图片字段”。如果你的图片字段允许多张图片,需要先读取原字段数组,再把新图片对象追加进去,最后仍然整体更新这个字段。
# -*-coding:utf-8-*-
import hashlib
import mimetypes
import uuid
from datetime import datetime
from pathlib import Path
from services.NormalType import NormalService
class DesktopUploadSvc(NormalService):
def uploadImageToModelField(self, file, modelFullName, pk, fieldName):
fileItem = file[0] if isinstance(file, list) else file
originalFileName = fileItem.filename
data = fileItem.stream.read()
if not data:
return {"status": False, "error": "文件内容为空"}
md5 = hashlib.md5(data).hexdigest()
suffix = Path(originalFileName).suffix
storedFileName = f"{md5}{suffix}"
contentType = mimetypes.guess_type(originalFileName)[0] or "application/octet-stream"
storageSvc = app.getElement("storages.services.StorageSvc")
uploadResult = storageSvc.uploadByFile(
originalFileName,
data=data,
md5=md5,
contentType=contentType,
)
url = uploadResult.get("url") if isinstance(uploadResult, dict) else ""
if not url:
return {"status": False, "error": "文件上传失败,未返回 url"}
storageObj = storageSvc.getStorage()
storeFullName = getattr(storageObj, "name", "") or "storages.Default"
imageUid = uuid.uuid4().hex
imageItem = {
"uid": imageUid,
"name": storedFileName,
"fileName": originalFileName,
"url": url,
"md5": md5,
"size": len(data),
"type": contentType,
"storeFullName": storeFullName,
}
pkValue = int(pk) if isinstance(pk, str) and pk.isdigit() else pk
# 可选但推荐:创建平台文件记录,保持和网页端上传链路一致。
FileModel = app.getElement("storages.services.models.FileModel")
fileRow = FileModel.create({
"uid": imageUid,
"fileName": originalFileName,
"url": url,
"storeFullName": storeFullName,
"uploadTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"md5": md5,
"size": len(data),
})
if isinstance(fileRow, dict):
imageItem.update(fileRow)
model = app.getElement(modelFullName)
model.updateByPK(
pkList=[pkValue],
updateData={fieldName: [imageItem]},
triggerEvent=1,
)
return {
"status": True,
"data": imageItem,
}
服务函数需要在 services/DesktopUploadSvc/e.json 里声明。参数名要和 Python 函数一致:
{
"title": "桌面端图片上传服务",
"type": "services.NormalType",
"backendBundleEntry": ".",
"functionList": [
{
"name": "uploadImageToModelField",
"title": "上传图片到模型图片字段",
"args": [
{"name": "file", "title": "图片文件", "dataType": "File"},
{"name": "modelFullName", "title": "模型FullName", "dataType": "Stext"},
{"name": "pk", "title": "主键", "dataType": "Stext"},
{"name": "fieldName", "title": "图片字段名", "dataType": "Stext"}
],
"returnType": "JitDict"
}
]
}
上面的示例是覆盖写入:
updateData={fieldName: [imageItem]}
如果要追加到多图字段,不要只传一个对象,也不要把原值覆盖掉。应该先查出当前记录的图片字段值,得到原数组后再追加:
pkValue = int(pk) if isinstance(pk, str) and pk.isdigit() else pk
rowObj = model.queryset.get(**{model.pkName: pkValue}, level=0)
oldItems = rowObj.value.get(fieldName) if rowObj else []
oldItems = oldItems or []
model.updateByPK(
pkList=[pkValue],
updateData={fieldName: oldItems + [imageItem]},
triggerEvent=1,
)
这个接口给桌面软件调用时,一定要处理鉴权。通常有两种方式:
第二种方式需要在这个服务函数的 e.json 函数声明里加:
{
"ignoreSign": true,
"loginRequired": false
}
然后在请求拦截器里校验类似这样的请求头:
Authorization: Bearer <your-token>
注意:不要只关闭平台校验而不加自己的拦截器,否则上传接口会变成未鉴权接口。
另外,生产环境不建议完全相信客户端传入的 modelFullName 和 fieldName。最好在服务函数里做白名单,只允许更新指定模型的指定图片字段。
自定义请求鉴权可以参考:
用户不需要直接写 OSS 和数据库表。正确路径是:
桌面软件上传图片
-> 调用应用后端服务函数
-> 服务函数调用 StorageSvc.uploadByFile 上传文件
-> 服务函数按平台格式生成图片对象数组
-> 可选但推荐:通过 FileModel.create 创建平台文件记录
-> 服务函数调用 model.updateByPK 写入图片字段
这样前端图片字段可以正常展示,后续预览、编辑和文件管理链路也更接近网页端上传的行为。