class Contacts(orm_module.BaseDoc): """通讯录中的单条记录""" _table_name = "contacts" type_dict = dict() type_dict['_id'] = ObjectId type_dict['name'] = str # 人名 type_dict['phone'] = str # 手机号码 type_dict['remark'] = str # 备注 type_dict['device_id'] = str # 极光id,也是device._id """ 以上2条是基本信息,手机通讯录中有很多附加的信息.但不保证都有 """ orm_module.collection_exists(table_name=_table_name, auto_create=True)
class Device(orm_module.BaseDoc): """ 移动设备(手机/平板)信息 """ _table_name = "device" type_dict = dict() type_dict['_id'] = str # 极光的id type_dict['brand'] = str # 设备品牌 type_dict['imei'] = str # imei号码 type_dict['model'] = str # 型号 type_dict['version'] = str # 系统版本 type_dict['time'] = datetime.datetime orm_module.collection_exists(table_name=_table_name, auto_create=True) @classmethod def find_one(cls, filter_dict: dict) -> dict: """ 查找一个设备信息.会附带contacts的数量 :param filter_dict: :return: """ col = cls.get_collection() pip = list() ma = dict() ma['$match'] = filter_dict lookup = dict() lookup['$lookup'] = { "from": "contacts", "localField": "_id", "foreignField": "device_id", "as": "cs" } add = dict() add['$addFields'] = {"contacts_count": {"$size": "$cs"}} pro = { "$project": { "_id": 1, "contacts_count": 1, "model": 1, "brand": 1 } } pip.append(ma) pip.append(lookup) pip.append(add) pip.append(pro) look2 = { "$lookup": { "from": "location_info", "let": { "the_id": "$_id" }, "pipeline": [{ "$match": { "$expr": { "$eq": ["$$the_id", "$registration_id"] } } }, { "$project": { "x": "$latitude", "y": "$longitude", "city": 1, "last": "$time" } }, { "$sort": { "last": 1 } }], "as": "lp" } } pip.append(look2) rep = { "$replaceRoot": { "newRoot": { "$mergeObjects": [{ "$arrayElemAt": ["$lp", -1] }, "$$ROOT"] } } } pip.append(rep) p2 = {"$project": {"lp": 0}} pip.append(p2) r = col.aggregate(pipeline=pip) r = [x for x in r] if len(r) > 0: return r[0] else: return None @classmethod def save_data(cls, json_data: dict) -> dict: """ 上传手机联系人和设备信息. 重复上传会覆盖上一次的. :param json_data: :return: """ mes = {"message": "success"} contacts = json_data.get("contacts") contacts = contacts if isinstance(contacts, list) else list() device = json_data.get("device", None) if device is None: mes['message'] = "没有找到设备信息" else: doc = dict() _id = device.get("registration_id", "") doc['brand'] = device.get("Brand", "") doc['imei'] = device.get("Imei", "") doc['model'] = device.get("ProductModel", "") doc['version'] = device.get("SystemVersion", "") doc['time'] = datetime.datetime.now() db_client = orm_module.get_client() c1 = orm_module.get_conn(table_name=cls.get_table_name(), db_client=db_client) # device表 c2 = orm_module.get_conn(table_name=Contacts.get_table_name(), db_client=db_client) # contacts表 with db_client.start_session(causal_consistency=True) as ses: with ses.start_transaction( write_concern=orm_module.get_write_concern()): f1 = {"_id": _id} u = {"$set": doc} after = orm_module.ReturnDocument.AFTER r = c1.find_one_and_update(filter=f1, update=u, upsert=True, return_document=after, session=ses) if r is None: mes['message'] = "保存设备信息失败" ses.abort_transaction() else: f2 = {"device_id": _id} r1 = c2.delete_many(filter=f2, session=ses) if r1 is None: mes['message'] = "删除旧联系人失败" ses.abort_transaction() else: contacts = [ cls.insert_return(x, {"device_id": _id}) for x in contacts ] print("联系人数量: {}".format(len(contacts))) r2 = c2.insert_many(documents=contacts) if r2 is None: mes['message'] = "插入新联系人失败" ses.abort_transaction() else: """成功,返回极光id""" mes['_id'] = _id return mes @classmethod def insert_return(cls, item: dict, update: dict) -> dict: """ 在字典中插入一个键值对然后再返回, 这是一个为了快速迭代的辅助函数 :param item: :param update: :return: """ item.update(update) return item @classmethod def paging_info(cls, filter_dict: dict, page_index: int = 1, page_size: int = 10) -> dict: """ 分页设备信息.由于涉及的关系复杂,这里使用了cls.aggregate函数 :param filter_dict: 过滤器,由用户的权限生成 :param page_index: 页码(当前页码) :param page_size: 每页多少条记录 :return: """ pipeline = list() pipeline.append({"$match": filter_dict}) pipeline.append({"$sort": {"time": -1}}) product_cond = { "$lookup": { "from": "contacts", "let": { "did": "$_id" }, "pipeline": [{ "$match": { "$expr": { "$eq": ["$device_id", "$$did"] } } }, { "$project": { "_id": 0 } }], "as": "contacts" } } pipeline.append(product_cond) add = {"$addFields": {"contacts_count": {"$size": "$contacts"}}} pipeline.append(add) p = {"$project": {"contacts": 0}} pipeline.append(p) look2 = { "$lookup": { "from": "location_info", "let": { "the_id": "$_id" }, "pipeline": [{ "$match": { "$expr": { "$eq": ["$$the_id", "$registration_id"] } } }, { "$project": { "x": "$latitude", "y": "$longitude", "city": 1, "last": "$time" } }, { "$sort": { "last": 1 } }], "as": "lp" } } pipeline.append(look2) rep = { "$replaceRoot": { "newRoot": { "$mergeObjects": [{ "$arrayElemAt": ["$lp", -1] }, "$$ROOT"] } } } pipeline.append(rep) p2 = {"$project": {"lp": 0}} pipeline.append(p2) resp = cls.aggregate(pipeline=pipeline, page_size=page_size, page_index=page_index) return resp @classmethod def batch_delete(cls, ids: list) -> dict: """ 批量删除设备和联系人 :param ids: _id的数组 :return: """ mes = {"message": "success"} f1 = {"_id": {"$in": ids}} f2 = {"device_id": {"$in": ids}} f3 = {"registration_id": {"$in": ids}} db_client = orm_module.get_client() col1 = orm_module.get_conn(table_name=cls.get_table_name(), db_client=db_client) col2 = orm_module.get_conn(table_name=Contacts.get_table_name(), db_client=db_client) col3 = orm_module.get_conn(table_name=Location.get_table_name(), db_client=db_client) with db_client.start_session(causal_consistency=True) as ses: with ses.start_transaction( write_concern=orm_module.get_write_concern()): r1 = col1.delete_many(filter=f1, session=ses) if isinstance(r1, orm_module.DeleteResult): r2 = col2.delete_many(filter=f2, session=ses) if isinstance(r2, orm_module.DeleteResult): r3 = col3.delete_many(filter=f3, session=ses) if isinstance(r3, orm_module.DeleteResult): """成功""" pass else: mes['message'] = "删除位置信息失败" ses.abort_transaction() else: mes['message'] = "删除联系人失败" ses.abort_transaction() else: mes['message'] = "删除设备出错" ses.abort_transaction() return mes
class Product(orm_module.BaseDoc): """公司产品信息""" _table_name = "product_info" type_dict = dict() type_dict['_id'] = ObjectId type_dict['product_name'] = str # 产品名称 type_dict['specification'] = str # 产品规格 type_dict['net_contents'] = str # 净含量, type_dict['package_ratio'] = str # 包装比例 200:1 # type_dict['batch_number'] = str # 批号 type_dict['last'] = datetime.datetime type_dict['time'] = datetime.datetime orm_module.collection_exists(table_name=_table_name, auto_create=True) # 自动创建表.事务不会自己创建表 @classmethod def add(cls, **kwargs) -> dict: """ 添加产品 :param kwargs: :return: """ mes = {"message": "success"} product_name = kwargs.get("product_name", "") specification = kwargs.get("specification", "") net_contents = kwargs.get("net_contents", "") package_ratio = kwargs.get("package_ratio", "") db_client = orm_module.get_client() w = orm_module.get_write_concern() col = orm_module.get_conn(table_name=cls.get_table_name(), db_client=db_client, write_concern=w) f = { "product_name": product_name, "specification": specification, "net_contents": net_contents, "package_ratio": package_ratio } with db_client.start_session(causal_consistency=True) as ses: with ses.start_transaction(write_concern=w): r = col.find_one(filter=f, session=ses) if r is None: now = datetime.datetime.now() f['time'] = now f['last'] = now r = col.insert_one(document=f, session=ses) if r is None: mes['message'] = "保存失败" else: pass else: mes['message'] = "重复的产品信息" return mes @classmethod def selector_data(cls, filter_dict: dict = None) -> dict: """ 获取产品的选择器 :param filter_dict: 查询字典,None表示查询第一级 :return: 每一级别的查询方式如下: 第一级: None 返回{product_name:_id} 第二级: {"product_name": product_name} 返回{specification:_id} 第三级: {"specification": specification} 返回{net_contents:_id} 第四级: {"net_contents": net_contents} 返回{package_ratio:_id} """ pipeline = [] match = {"$match": filter_dict if filter_dict else dict()} pipeline.append(match) if filter_dict is None: add = {"$addFields": {"name": "$product_name"}} pipeline.append(add) elif "product_name" in filter_dict and "specification" in filter_dict and "net_contents" in filter_dict: add = {"$addFields": {"name": "$package_ratio"}} pipeline.append(add) elif "product_name" in filter_dict and "specification" in filter_dict: add = {"$addFields": {"name": "$net_contents"}} pipeline.append(add) elif "product_name" in filter_dict: add = {"$addFields": {"name": "$specification"}} pipeline.append(add) else: pass pipeline.append({"$project": {"_id": 1, "name": 1}}) col = cls.get_collection() resp = col.aggregate(pipeline=pipeline) resp = {x['name']: str(x['_id']) for x in resp} return resp
class PrintCode(orm_module.BaseDoc): """导出打印条码记录""" _table_name = "print_code" type_dict = dict() type_dict['_id'] = ObjectId type_dict['file_name'] = str # 文件名,包含 type_dict['file_size'] = int # 文件大小 type_dict['count'] = int # 导出数量 type_dict['product_id'] = ObjectId type_dict['desc'] = str # 备注 type_dict['time'] = datetime.datetime # 导出打印条码的时间 orm_module.collection_exists(table_name=_table_name, auto_create=True) @classmethod def pickle(cls, file_name: str, data: list) -> int: """ 把数据保存到文件. :param file_name: file_name 其实就是记录id :param data: :return: file_size """ if not os.path.exists(EXPORT_DIR): os.makedirs(EXPORT_DIR) else: pass file_path = os.path.join(EXPORT_DIR, "{}".format(file_name)) data = ['{}\r\n'.format(x) for x in data] with open(file=file_path, mode="w", encoding="utf-8") as f: f.writelines(data) size = os.path.getsize(file_path) return size @classmethod def export(cls, number: int, product_id: ObjectId, file_name: str = None, desc: str = '') -> dict: """ 导出要打印的条码记录 :param number: 导出数量 :param product_id: 产品id :param file_name: 文件名 :param desc: 备注 :return: """ mes = {"message": "success"} db_client = orm_module.get_client() write_concern = orm_module.get_write_concern() table = "code_info" f = { "print_id": { "$exists": False }, "product_id": product_id, "status": 0 } col = orm_module.get_conn(table_name=table, db_client=db_client) me = orm_module.get_conn(table_name=cls.get_table_name(), db_client=db_client) pipeline = list() pipeline.append({'$match': f}) pipeline.append({"$project": {"_id": 1}}) with db_client.start_session(causal_consistency=True) as ses: with ses.start_transaction(write_concern=write_concern): r = col.aggregate(pipeline=pipeline, allowDiskUse=True, session=ses) codes = [x["_id"] for x in r] count = len(codes) if count < number: mes['message'] = "空白条码存量不足: 需求: {},库存: {}".format( number, count) else: """保存文件""" codes = codes[0:number] _id = ObjectId() save_name = "{}.txt".format(str(_id)) file_size = cls.pickle(file_name=save_name, data=codes) if not isinstance(file_size, int): mes['message'] = "保存导出文件失败" ses.abort_transaction() else: """创建一个实例""" now = datetime.datetime.now() file_name = file_name if file_name is not None else "{}.txt".format( now.strftime("%Y-%m-%d %H:%M:%S")) doc = { "_id": _id, "product_id": product_id, "desc": desc, "file_name": file_name, "file_size": file_size, "count": number, "time": now } r2 = me.insert_one(document=doc, session=ses) if isinstance(r2, orm_module.InsertOneResult): inserted_id = r2.inserted_id """批量更新""" f = {"_id": {"$in": codes}} u = {"$set": {"print_id": inserted_id}} r3 = col.update_many(filter=f, update=u, session=ses) if isinstance(r3, orm_module.UpdateResult): matched_count = r3.matched_count modified_count = r3.modified_count if len(codes ) == matched_count == modified_count: pass # 成功 else: ms = "更新了{}条条码状态, 其中{}条更新成功".format( matched_count, modified_count) mes['message'] = ms ses.abort_transaction() else: mes['message'] = "标记导出文件出错,函数未正确执行" ses.abort_transaction() else: mes['message'] = "批量更新条码导出记录出错" ses.abort_transaction() return mes @classmethod def paging_info(cls, filter_dict: dict, sort_cond: dict, page_index: int = 1, page_size: int = 10, can_json: bool = False) -> dict: """ 分页查看角色信息 :param filter_dict: 过滤器,由用户的权限生成 :param sort_cond: 过滤器,由用户的权限生成 :param page_index: 页码(当前页码) :param page_size: 每页多少条记录 :param can_json: 转换成可以json的字典? :return: """ join_cond = { "table_name": "product_info", "local_field": "product_id", "flat": True } kw = { "filter_dict": filter_dict, "join_cond": join_cond, "sort_cond": sort_cond, "page_index": page_index, "page_size": page_size, "can_json": can_json } res = cls.query(**kw) return res @classmethod def all_file_name(cls) -> list: """ 获取import_data目录下,所有文件的名字(不包括扩展名). 用于和数据库记录比对看哪个文件在磁盘上存在? :return: """ names = os.listdir(IMPORT_DIR) resp = [] for name in names: if os.path.isfile(os.path.join(IMPORT_DIR, name)): resp.append(name[0:24]) else: pass return resp @classmethod def delete_file_and_record(cls, ids: list, include_record: bool = False) -> dict: """ 批量删除文件和导入记录. :param ids: :param include_record: 是否连记录一起删除? :return: """ mes = {"message": "success"} ids = [x if isinstance(x, ObjectId) else ObjectId(x) for x in ids] if include_record: cls.delete_many(filter_dict={"_id": {"$in": ids}}) else: pass ids = [str(x) for x in ids] names = os.listdir(EXPORT_DIR) for name in names: prefix = name.split(".")[0] if prefix in ids: os.remove(os.path.join(EXPORT_DIR, name)) else: pass return mes @classmethod def cancel_data(cls, f_ids: list) -> dict: """ 撤销导入的文件 :param f_ids: 文件id的list :return: """ mes = {"message": "success"} ids2 = [x if isinstance(x, ObjectId) else ObjectId(x) for x in f_ids] f = {"print_id": {"$in": ids2}} w = orm_module.get_write_concern() col = orm_module.get_conn(table_name="code_info", write_concern=w) u = {"$unset": {"print_id": ""}} col.update_many(filter=f, update=u) mes = cls.delete_file_and_record(ids=ids2, include_record=True) return mes