Files
along_django/peizhi/xcx_config_views.py

214 lines
9.4 KiB
Python

"""小程序系统配置 & 页面资源 — 后台管理接口"""
import logging
from django.db import transaction
from rest_framework import permissions, status
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
from peizhi.models import XcxSysPeizhi, XcxPageZiyuan
from utils.oss_utils import upload_to_oss, validate_image
from utils.page_ziyuan_service import DEFAULT_ZIYUAN, build_page_assets_for_api, clear_page_ziyuan_cache
from utils.xcx_sys_config import clear_xcx_sys_peizhi_cache, wx_cfg
from yonghu.models import AdminProfile
logger = logging.getLogger(__name__)
def _verify_admin(request, zhanghao):
if not zhanghao:
return False, Response({'code': 1, 'msg': '账号不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST)
user_main = request.user
if user_main.phone != zhanghao or user_main.user_type != 'admin':
return False, Response({'code': 1, 'msg': '无权限', 'data': None}, status=status.HTTP_401_UNAUTHORIZED)
try:
user_main.admin_profile
except AdminProfile.DoesNotExist:
return False, Response({'code': 1, 'msg': '无权限', 'data': None}, status=status.HTTP_401_UNAUTHORIZED)
return True, None
def _mask_secret(val):
if not val:
return ''
if len(val) <= 8:
return '****'
return val[:4] + '****' + val[-4:]
class XcxSysPeizhiQueryView(APIView):
"""POST /peizhi/xcxsyshq — 管理员获取小程序系统配置"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
ok, err = _verify_admin(request, request.data.get('zhanghao'))
if not ok:
return err
row = XcxSysPeizhi.objects.first()
data = {
'weixin_appid': row.weixin_appid if row else wx_cfg.WEIXIN_APPID,
'weixin_secret': _mask_secret(row.weixin_secret if row else wx_cfg.WEIXIN_SECRET),
'weixin_official_appid': row.weixin_official_appid if row else wx_cfg.WEIXIN_OFFICIAL_APPID,
'weixin_official_secret': _mask_secret(
row.weixin_official_secret if row else wx_cfg.WEIXIN_OFFICIAL_SECRET
),
'weixin_official_token': row.weixin_official_token if row else wx_cfg.WEIXIN_OFFICIAL_TOKEN,
'weixin_official_encoding_aes_key': _mask_secret(
row.weixin_official_encoding_aes_key if row else wx_cfg.WEIXIN_OFFICIAL_ENCODING_AES_KEY
),
'weixin_template_id': row.weixin_template_id if row else wx_cfg.WEIXIN_TEMPLATE_ID,
'weixin_template_max_per_minute': (
row.weixin_template_max_per_minute if row else wx_cfg.WEIXIN_TEMPLATE_MAX_PER_MINUTE
),
'weixin_template_batch_size': (
row.weixin_template_batch_size if row else wx_cfg.WEIXIN_TEMPLATE_BATCH_SIZE
),
'weixin_broadcast_enabled': (
row.weixin_broadcast_enabled if row else wx_cfg.WEIXIN_BROADCAST_ENABLED
),
'weixin_broadcast_workers': row.weixin_broadcast_workers if row else wx_cfg.WEIXIN_BROADCAST_WORKERS,
'weixin_mchid': row.weixin_mchid if row else wx_cfg.WEIXIN_MCHID,
'weixin_shanghu_miyao': _mask_secret(row.weixin_shanghu_miyao if row else wx_cfg.WEIXIN_SHANGHUMIYAO),
'weixin_notify_url': row.weixin_notify_url if row else wx_cfg.WEIXIN_NOTIFY_URL,
'weixin_cert_path': row.weixin_cert_path if row else wx_cfg.WEIXIN_CERT_PATH,
'weixin_key_path': row.weixin_key_path if row else wx_cfg.WEIXIN_KEY_PATH,
'notify_base_url': row.notify_base_url if row else wx_cfg.NOTIFY_BASE_URL,
'from_database': bool(row),
}
return Response({'code': 0, 'msg': 'success', 'data': data})
class XcxSysPeizhiUpdateView(APIView):
"""POST /peizhi/xcxsysgx — 管理员更新小程序系统配置"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
UPDATABLE = frozenset({
'weixin_appid', 'weixin_secret', 'weixin_official_appid', 'weixin_official_secret',
'weixin_official_token', 'weixin_official_encoding_aes_key', 'weixin_template_id',
'weixin_template_max_per_minute', 'weixin_template_batch_size', 'weixin_broadcast_enabled',
'weixin_broadcast_workers', 'weixin_mchid', 'weixin_shanghu_miyao', 'weixin_notify_url',
'weixin_cert_path', 'weixin_key_path', 'notify_base_url',
})
def post(self, request):
ok, err = _verify_admin(request, request.data.get('zhanghao'))
if not ok:
return err
payload = request.data.get('config') or request.data
with transaction.atomic():
row, _ = XcxSysPeizhi.objects.get_or_create(id=1)
for key in self.UPDATABLE:
if key in payload and payload[key] is not None:
val = payload[key]
if key.endswith('_secret') or key.endswith('_miyao') or key.endswith('_aes_key'):
if val == '' or '****' in str(val):
continue
if key == 'weixin_broadcast_enabled':
row.weixin_broadcast_enabled = bool(val)
elif key in ('weixin_template_max_per_minute', 'weixin_template_batch_size', 'weixin_broadcast_workers'):
row.__setattr__(key, int(val))
else:
row.__setattr__(key, str(val).strip())
row.save()
clear_xcx_sys_peizhi_cache()
return Response({'code': 0, 'msg': '配置已更新', 'data': None})
class XcxPageZiyuanQueryView(APIView):
"""POST /peizhi/xcxyzycx — 管理员查询页面资源列表"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
ok, err = _verify_admin(request, request.data.get('zhanghao'))
if not ok:
return err
zu = (request.data.get('ziyuan_zu') or '').strip()
merged = build_page_assets_for_api()
if zu:
groups = {zu: merged.get(zu, {})}
else:
groups = merged
items = []
for gzu, keys in groups.items():
for key, path in keys.items():
items.append({
'ziyuan_zu': gzu,
'ziyuan_key': key,
'ziyuan_path': path,
'miaoshu': DEFAULT_ZIYUAN.get(f'{gzu}.{key}', ''),
})
return Response({'code': 0, 'msg': 'success', 'data': {'list': items, 'groups': groups}})
class XcxPageZiyuanUpdateView(APIView):
"""POST /peizhi/xcxyzgx — 管理员更新页面资源路径"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
ok, err = _verify_admin(request, request.data.get('zhanghao'))
if not ok:
return err
items = request.data.get('items') or []
if not isinstance(items, list) or not items:
return Response({'code': 1, 'msg': 'items 不能为空', 'data': None}, status=400)
with transaction.atomic():
for item in items:
zu = (item.get('ziyuan_zu') or '').strip()
key = (item.get('ziyuan_key') or '').strip()
path = (item.get('ziyuan_path') or '').strip().lstrip('/')
if not zu or not key or not path:
continue
XcxPageZiyuan.objects.update_or_create(
ziyuan_zu=zu, ziyuan_key=key,
defaults={'ziyuan_path': path, 'miaoshu': item.get('miaoshu') or ''},
)
clear_page_ziyuan_cache()
return Response({'code': 0, 'msg': '资源已更新', 'data': None})
class XcxPageZiyuanUploadView(APIView):
"""POST /peizhi/xcxyzsc — 管理员上传页面资源图片"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = (MultiPartParser, FormParser)
def post(self, request):
ok, err = _verify_admin(request, request.data.get('zhanghao'))
if not ok:
return err
zu = (request.data.get('ziyuan_zu') or '').strip()
key = (request.data.get('ziyuan_key') or '').strip()
image_file = request.FILES.get('image')
if not zu or not key:
return Response({'code': 1, 'msg': 'ziyuan_zu/ziyuan_key 必填', 'data': None}, status=400)
if not image_file:
return Response({'code': 2, 'msg': '请选择图片', 'data': None}, status=400)
is_valid, error_msg = validate_image(image_file)
if not is_valid:
return Response({'code': 3, 'msg': error_msg, 'data': None}, status=400)
default_key = f'{zu}.{key}'
default_path = DEFAULT_ZIYUAN.get(default_key, f'beijing/{zu}/{key}.png')
oss_path = default_path
image_file.seek(0)
full_url = upload_to_oss(image_file, oss_path)
if not full_url:
return Response({'code': 4, 'msg': '上传失败', 'data': None}, status=500)
with transaction.atomic():
XcxPageZiyuan.objects.update_or_create(
ziyuan_zu=zu, ziyuan_key=key,
defaults={'ziyuan_path': oss_path},
)
clear_page_ziyuan_cache()
return Response({'code': 0, 'msg': '上传成功', 'data': {'ziyuan_path': oss_path}})