import asyncio from datetime import datetime, timezone from fastapi.logger import logger from sqlalchemy import and_ from shared.constants import PAGE_SIZE from modules.configuration.schemas import ConfigForm from modules.configuration.models import Config from modules.configuration.models import ConfigItem from modules.configuration.repository import config_repo, configitem_repo from config import FASTAPI_IMAGE_URL from core import R from utils.rich_text import get_file_url, save_file from utils.request import like_contains from core.security import get_user_id # 配置表单验证 def get_config_page(request): # 页码 page = int(request.query_params.get("pageNo", 1)) # 每页数 limit = int(request.query_params.get("pageSize", PAGE_SIZE)) # 实例化查询对象 query = config_repo.filter(Config.is_delete == 0) # 配置名称 name = request.query_params.get('name') if name: query = query.filter(Config.name.like(like_contains(name), escape='\\')) # 排序 query = query.order_by(Config.sort.asc()) # 记录总数 count = query.count() # 分页查询 config_list = query.limit(limit).offset((page - 1) * limit).all() # 若当前页超出末页(数据被并发删除),自动回退到第一页 if page > 1 and not config_list: page = 1 count = query.count() config_list = query.limit(limit).offset(0).all() # 实例化结果 result = [] # 遍历数据源 if len(config_list) > 0: for item in config_list: # 对象转字典 data = item.to_dict() # 加入列表 result.append(data) # 返回结果 return R.ok(data=result, count=count) # 根据ID获取配置详情 def get_config_detail(config_id): # 根据ID查询配置 config = config_repo.get_by_id(config_id) # 查询结果判空 if not config: return None # 对象转字典 data = config.to_dict() # 返回结果 return data # 添加配置 def add_config(request, data: ConfigForm): # 唯一性校验 if config_repo.filter(and_(Config.name == data.name, Config.is_delete == 0)).first(): return R.failed("配置名称不能重复") # 表单数据赋值给对象 config_repo.create(**data.model_dump(exclude={'id'}), create_user=get_user_id(request)) # 返回结果 return R.ok(msg="添加成功") # 更新配置 def update_config(request, data: ConfigForm): # 记录ID判空 if not data.id or data.id <= 0: return R.failed("记录ID不能为空") # 唯一性校验(排除自身) if config_repo.filter(and_(Config.name == data.name, Config.id != data.id, Config.is_delete == 0)).first(): return R.failed("配置名称不能重复") # 根据ID查询记录 config = config_repo.get_by_id(data.id) # 查询结果判空 if not config: return R.failed("记录不存在") # 构造更新数据(排除 id) updData = data.model_dump(exclude={'id'}) updData['update_user'] = get_user_id(request) updData['update_time'] = datetime.now(tz=timezone.utc) result = config_repo.update(data.id, updData) # 提交数据 if not result: return R.failed("更新失败") # 返回结果 return R.ok(msg="更新成功") # 删除配置 def delete_config(config_id): return Config.batch_delete_r(config_id) # 解析配置项选项字符串为字典 def _parse_options(options_str): """将 'k1=v1,k2=v2' 格式的选项字符串解析为字典""" if not options_str: return {} result = {} for part in options_str.split(','): parts = part.split('=', 1) if len(parts) == 2: result[parts[0]] = parts[1] return result # 获取网站配置信息 def get_config_info(request): # 一次性查询所有配置组 configList = config_repo.filter(Config.is_delete == 0).order_by(Config.sort.asc()).all() if not configList: return [] # 一次性查询所有活跃配置项,按config_id分组 config_ids = [c.id for c in configList] all_items = configitem_repo.filter( and_(ConfigItem.is_delete == 0, ConfigItem.status == 1, ConfigItem.config_id.in_(config_ids)) ).order_by(ConfigItem.sort.asc()).all() items_map = {} for item in all_items: items_map.setdefault(item.config_id, []).append(item) config_list = [] for val in configList: config = { 'config_id': val.id, 'config_name': val.name } data_list = [] for v in items_map.get(val.id, []): data = { "id": v.id, "title": v.title, "code": v.code, "value": v.value, "type": v.type, } item_type = v.type if item_type == "checkbox": data['optionsList'] = _parse_options(v.options) data["value"] = v.value.split(',') if v.value else [] elif item_type in ("radio", "select"): data['optionsList'] = _parse_options(v.options) elif item_type == "image": if v.value: data["value"] = get_file_url(v.value) elif item_type == "images": if v.value: data["value"] = [get_file_url(img) for img in v.value.split(',')] data_list.append(data) config['data_list'] = data_list config_list.append(config) return config_list # 保存网站配置信息 async def save_config_info(request): # 获取请求参数 req_data = await request.json() if not req_data or not isinstance(req_data, dict): return R.failed("请求参数异常") def _save(): # 预加载所有活跃配置项,仅允许修改这些项 valid_items = configitem_repo.filter( and_(ConfigItem.is_delete == 0, ConfigItem.status == 1) ).all() item_map = {item.code: item for item in valid_items} # 数据源处理 for key, val in req_data.items(): # 仅允许修改已存在的活跃配置项,忽略非法key config_item = item_map.get(key) if not config_item: continue # 图片数据 item = [] if isinstance(val, list): for v in val: if isinstance(v, str) and (v.startswith("http://") or v.startswith("https://")): url = save_file(v, "config") item.append(url) else: item.append(v) val = ','.join(item) else: if isinstance(val, str) and (val.startswith("http://") or val.startswith("https://")): val = save_file(val, "config") # 设置 config_item.value = val # 同步DB操作与网络下载放入线程池,避免阻塞事件循环 await asyncio.to_thread(_save) # 返回结果 return R.ok() from datetime import datetime, timezone from fastapi.logger import logger from sqlalchemy import and_ from shared.enums import CONFIG_DATA_TYPE_LIST from shared.constants import PAGE_SIZE from modules.configuration.schemas import ConfigItemForm, ConfigItemStatusForm from modules.configuration.models import ConfigItem from core import R from core.security import get_user_id from utils.request import like_contains # 查询配置项分页数据 def get_config_item_page(request): # 页码 page = int(request.query_params.get("pageNo", 1)) # 每页数 limit = int(request.query_params.get("pageSize", PAGE_SIZE)) # 实例化查询对象 query = configitem_repo.filter(ConfigItem.is_delete == 0) # 配置ID config_id = request.query_params.get('config_id', 0) query = query.filter(ConfigItem.config_id == config_id) # 配置项项名称模糊筛选 title = request.query_params.get('title') if title: query = query.filter(ConfigItem.title.like(like_contains(title), escape='\\')) # 排序 query = query.order_by(ConfigItem.sort.asc()) # 记录总数 count = query.count() # 分页查询 config_list = query.limit(limit).offset((page - 1) * limit).all() # 若当前页超出末页(数据被并发删除),自动回退到第一页 if page > 1 and not config_list: page = 1 count = query.count() config_list = query.limit(limit).offset(0).all() # 实例化结果 result = [] # 遍历数据源 if len(config_list) > 0: for item in config_list: # 对象转字典 data = item.to_dict() # 配置项类型 data['type_name'] = CONFIG_DATA_TYPE_LIST.get(item.type) # 加入列表 result.append(data) # 返回结果 return R.ok(data=result, count=count) # 根据ID查询配置项详情 def get_config_item_detail(config_id): # 根据ID查询配置项 config_data = configitem_repo.get_by_id(config_id) # 查询结果判空 if not config_data: return None # 对象转字典 data = config_data.to_dict() # 返回结果 return data # 添加配置项 def add_config_item(request, data: ConfigItemForm): # 表单数据赋值给对象 configitem_repo.create(**data.model_dump(exclude={'id'}), create_user=get_user_id(request)) # 返回结果 return R.ok(msg="添加成功") # 更新配置项 def update_config_item(request, data: ConfigItemForm): # 记录ID判空 if not data.id or data.id <= 0: return R.failed("记录ID不能为空") # 根据ID查询记录 config_data = configitem_repo.get_by_id(data.id) # 查询结果判空 if not config_data: return R.failed("记录不存在") # 构造更新数据(排除 id) updData = data.model_dump(exclude={'id'}) updData['update_user'] = get_user_id(request) updData['update_time'] = datetime.now(tz=timezone.utc) result = configitem_repo.update(data.id, updData) # 提交数据 if not result: return R.failed("更新失败") # 返回结果 return R.ok(msg="更新成功") # 删除配置项 def delete_config_item(config_id): return ConfigItem.batch_delete_r(config_id) # 设置状态 def update_config_item_status(request, data: ConfigItemStatusForm): # 根据ID查询记录 config_data = configitem_repo.get_by_id(data.id) # 查询结果判空 if not config_data: return R.failed("记录不存在") # 更新记录 result = configitem_repo.update(data.id, { "status": data.status, "update_user": get_user_id(request), "update_time": datetime.now(tz=timezone.utc) }) # 提交数据 if not result: return R.failed("设置失败") # 返回结果 return R.ok(msg="设置成功")