nacos自定义api客户端

# !/usr/bin/env python
# -*- coding: utf8 -*-

from nacos import NacosException, NacosClient
from nacos.client import process_common_config_params
from nacos.params import *
from nacos.files import *
from nacos.commons import *
from urllib.error import HTTPError
from http import HTTPStatus
from pyfsops import success, EXCEPTIONS
import requests, json, random, logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class NacosApi(NacosClient):
    def __init__(self, server_addresses, username='nacos', password='nacos', namespace='public'):
        super(NacosClient, self).__init__()
        NacosClient.__init__(self, server_addresses, namespace=namespace, username=username,
                             password=password)

        self.server_addresses = server_addresses

    def add_naming_instance(self, service_name, ip, port, cluster_name=None, weight=1.0, metadata=None,
                            enable=True, healthy=True, ephemeral=True, group_name='DEFAULT_GROUP'):
        '''注册实例'''
        params = {
            "ip": ip,
            "port": port,
            "serviceName": service_name,
            "weight": weight,
            "enable": enable,
            "healthy": healthy,
            "clusterName": cluster_name,
            "ephemeral": ephemeral,
            "groupName": group_name,
            "username": self.username,
            "password": self.password,
        }
        self._build_metadata(metadata, params)

        if self.namespace:
            params["namespaceId"] = self.namespace

        try:
            resp = self._do_sync_req("/nacos/v2/ns/instance", None, None,
                                     params, self.default_timeout, "POST", "naming")
            c = resp.read()
            return c.decode()
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            # self.logger.exception("[add-naming-instance] exception %s occur" % str(e))
            raise Exception("[add-naming-instance] exception %s occur" % str(e))


    def query_nacos_instance(self, serviceName, clusterName='DEFAULT', groupName='DEFAULT_GROUP'):
        try:
            rq = self.list_naming_instance(service_name=serviceName, clusters=clusterName, group_name=groupName,
                                           healthy_only=True)
            ret = success.copy()
            ret['data'] = rq['hosts']
        except (Exception, ConnectionError, NacosException) as e:
            ret = EXCEPTIONS.code['error'].copy()
            ret['message'] = '查询 NACOS 服务 {0} 失败,原因:{1}'.format(serviceName, e)
        return ret

    def get_accessToken(self):
        try:
            nacos_address = random.choice(self.server_addresses.split(','))
            login_url = nacos_address + '/nacos/v1/auth/login'
            login_params = {'username': self.username, 'password': self.password}
            rq = requests.post(login_url, data=login_params)
            rs = rq.json()
            return rs
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except (Exception, ConnectionError) as e:
            raise NacosException("[get-accessToken] exception %s occur" % str(e))

    def list_naming_service_v2(self, clusters=None, namespace_id=None, group_name=None, pageNo=None, pageSize=None,
                               selector=None):
        """
        查询符合条件的服务列表
        :param clusters:            集群名称            字符串,多个集群用逗号分隔
        :param namespace_id:        命名空间ID
        :param group_name:          分组名
        :param pageNo:          当前页
        :param pageSize:          页条目数
        """
        params = {
            "selector": {
                "type": "none",  # 返回当前Selector类型
                "contextType": "NONE"  # 返回当前Selector所需的资源类型
            }
        }
        if pageNo and pageSize:
            params.update({"pageNo": pageNo, "pageSize": pageSize})
        if clusters is not None:
            params["clusters"] = clusters
        if selector is not None:
            # 查询服务列表的时候也有个名为selector的参数,作用是可以根据服务以及实例中的元数据参数metadata进行服务过滤。
            # 这个表达式为:{“type”:“label”,“expression”:“INSTANCE.metadata.xxx = ‘xxx’”} 或
            # {“type”:“label”,“expression”:“SERVICE.metadata.xxx = ‘xxx’”}。

            params["selector"] = selector

        namespace_id = namespace_id or self.namespace
        if namespace_id:
            params["namespaceId"] = namespace_id

        group_name = group_name or 'DEFAULT_GROUP'
        if group_name:
            params['groupName'] = group_name

        try:
            resp = self._do_sync_req("/nacos/v2/ns/service/list", None, params, None, self.default_timeout, "GET")
            c = resp.read()
            return json.loads(c.decode("UTF-8"))
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            raise NacosException("[list-naming-service] exception %s occur" % str(e))

    def get_naming_service(self, service_name, namespace_id=None, group_name=None):
        params = {
            "serviceName": service_name
        }
        params["namespaceId"] = namespace_id or self.namespace
        params['groupName'] = group_name or 'DEFAULT_GROUP'

        try:
            resp = self._do_sync_req("/nacos/v2/ns/service", None, params, None, self.default_timeout, "GET")
            c = resp.read()
            return json.loads(c.decode("UTF-8"))
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            elif e.code == 400:
                # 搜不到  code=400
                c = e.read()
                return json.loads(c.decode("UTF-8"))
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            raise NacosException("[get-naming-service] exception %s occur" % str(e))

    def list_naming_configs_v2(self, namespace_id=None):
        """
        获取指定命名空间下的配置信息列表
        :param namespace_id:        命名空间ID
        """
        params = {}

        namespace_id = namespace_id or self.namespace
        if namespace_id:
            params["namespaceId"] = namespace_id

        try:
            resp = self._do_sync_req("/nacos/v2/cs/history/configs", None, params, None, self.default_timeout, "GET")
            c = resp.read()
            return json.loads(c.decode("UTF-8"))
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            raise NacosException("[list-naming-configs] exception %s occur" % str(e))

    def list_namespaces_v2(self):
        """
        查询当前所有的命名空间
        """
        try:
            resp = self._do_sync_req("/nacos/v2/console/namespace/list", None, None, None, self.default_timeout, "GET")
            c = resp.read()
            return json.loads(c.decode("UTF-8"))
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            raise NacosException("[list-namespaces] exception %s occur" % str(e))

    def get_config_v2(self, data_id, group, namespace_id=None, timeout=None, no_snapshot=None):
        no_snapshot = self.no_snapshot if no_snapshot is None else no_snapshot
        data_id, group = process_common_config_params(data_id, group)

        params = {
            "dataId": data_id,
            "group": group,
        }
        namespace_id = namespace_id or self.namespace
        if namespace_id:
            params["namespaceId"] = namespace_id

        cache_key = group_key(data_id, group, self.namespace)
        # get from failover
        content = read_file_str(self.failover_base, cache_key)
        if content is None:
            logger.debug("[get-config] failover config is not exist for %s, try to get from server" % cache_key)
        else:
            logger.debug(
                "[get-config] get %s from failover directory, content is %s" % (cache_key, truncate(content)))
            return content

        # get from server
        try:
            resp = self._do_sync_req("/nacos/v2/cs/config", None, params, None, timeout or self.default_timeout)
            content = resp.read().decode("UTF-8")
        except HTTPError as e:
            if e.code == HTTPStatus.NOT_FOUND:
                logger.warning(
                    "[get-config] config not found for data_id:%s, group:%s, namespace:%s, try to delete snapshot" % (
                        data_id, group, self.namespace))
                self.delete_file(self.snapshot_base, cache_key)
                return None
            elif e.code == HTTPStatus.CONFLICT:
                logger.error(
                    "[get-config] config being modified concurrently for data_id:%s, group:%s, namespace:%s" % (
                        data_id, group, self.namespace))
            elif e.code == HTTPStatus.FORBIDDEN:
                logger.error("[get-config] no right for data_id:%s, group:%s, namespace:%s" % (
                    data_id, group, self.namespace))
                raise NacosException("Insufficient privilege.")
            else:
                logger.error("[get-config] error code [:%s] for data_id:%s, group:%s, namespace:%s" % (
                    e.code, data_id, group, self.namespace))
                if no_snapshot:
                    raise
        except Exception as e:
            logger.exception("[get-config] exception %s occur" % str(e))
            if no_snapshot:
                raise

        if no_snapshot:
            return content

        if content is not None:
            logger.info(
                "[get-config] content from server:%s, data_id:%s, group:%s, namespace:%s, try to save snapshot" % (
                    truncate(content), data_id, group, self.namespace))
            try:
                self.save_file(self.snapshot_base, cache_key, content)
            except Exception as e:
                logger.exception("[get-config] save snapshot failed for %s, data_id:%s, group:%s, namespace:%s" % (
                    data_id, group, self.namespace, str(e)))
            return content

        logger.error("[get-config] get config from server failed, try snapshot, data_id:%s, group:%s, namespace:%s" % (
            data_id, group, self.namespace))
        content = read_file_str(self.snapshot_base, cache_key)
        if content is None:
            logger.warning("[get-config] snapshot is not exist for %s." % cache_key)
        else:
            logger.debug(
                "[get-config] get %s from snapshot directory, content is %s" % (cache_key, truncate(content)))
            return content

    def list_naming_instance_v2(self, service_name, clusters=None, namespace_id=None, group_name=None,
                                healthy_only=False):
        """
        :param service_name:        服务名
        :param clusters:            集群名称            字符串,多个集群用逗号分隔
        :param namespace_id:        命名空间ID
        :param group_name:          分组名
        :param healthy_only:         是否只返回健康实例   否,默认为false
        """
        params = {
            "serviceName": service_name,
            "healthyOnly": healthy_only
        }

        if clusters is not None:
            params["clusters"] = clusters

        namespace_id = namespace_id or self.namespace
        if namespace_id:
            params["namespaceId"] = namespace_id

        group_name = group_name or 'DEFAULT_GROUP'
        if group_name:
            params['groupName'] = group_name

        try:

            resp = self._do_sync_req("/nacos/v2/ns/instance/list", None, params, None, self.default_timeout, "GET")
            c = resp.read()
            logger.info("[list-naming-instance] service_name:%s, namespace:%s, server response:%s" %
                        (service_name, self.namespace, c))
            return json.loads(c.decode("UTF-8"))
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            logger.exception("[list-naming-instance] exception %s occur" % str(e))
            raise

    def publish_config_v2(self, data_id, group, content, namespace_id=None, tag=None, configTags=None, app_name=None,
                          config_type=None, timeout=None, desc=None):
        '''

        :param namespaceId: 命名空间,默认为public与 ''相同
        :param data_id: 配置名
        :param group: 配置组名
        :param tag: 标签
        :param configTags: 配置标签列表,可多个,逗号分隔
        :param content:
        :param app_name: 应用名
        :param config_type: 配置类型
        :param desc : 配置描述
        :param timeout:
        :return:
        '''
        if content is None:
            raise NacosException("Can not publish none content, use remove instead.")

        data_id, group = process_common_config_params(data_id, group)
        if type(content) == bytes:
            content = content.decode("UTF-8")

        logger.info("[publish] data_id:%s, group:%s, namespace:%s, content:%s, timeout:%s" % (
            data_id, group, self.namespace, truncate(content), timeout))

        params = {
            "dataId": data_id,
            "group": group,
            "content": content.encode("UTF-8"),
            "username": self.username,
            "password": self.password
        }

        namespace_id = namespace_id or self.namespace
        if namespace_id:
            params["namespaceId"] = namespace_id

        if app_name: params["appName"] = app_name
        if desc: params["desc"] = desc
        if tag: params["tag"] = tag
        if configTags: params["configTags"] = configTags
        if config_type: params["type"] = config_type

        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        try:
            resp = self._do_sync_req("/nacos/v2/cs/config", headers, None, params,
                                     timeout or self.default_timeout, "POST")
            c = resp.read()
            logger.info("[publish] publish content, group:%s, data_id:%s, server response:%s" % (
                group, data_id, c))
            return c.decode()
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            logger.exception("[publish] exception %s occur" % str(e))
            raise


    def modify_naming_instance_v2(self, service_name, ip, port, cluster_name=None, weight=None, metadata=None,
                                  enable=None, ephemeral=True, group_name='DEFAULT_GROUP'):
        logger.info("[modify-naming-instance] ip:%s, port:%s, service_name:%s, namespace:%s" % (
            ip, port, service_name, self.namespace))

        params = {
            "ip": ip,
            "port": port,
            "serviceName": service_name,
            "ephemeral": ephemeral,
            "groupName": group_name
        }
        if cluster_name is not None:
            params["clusterName"] = cluster_name

        if enable is not None:
            params["enable"] = enable

        if weight is not None:
            params["weight"] = weight

        self._build_metadata(metadata, params)

        if self.namespace:
            params["namespaceId"] = self.namespace

        try:
            resp = self._do_sync_req("/nacos/v1/ns/instance", None, None, params, self.default_timeout, "PUT", "naming")
            c = resp.read()
            logger.info("[modify-naming-instance] ip:%s, port:%s, service_name:%s, namespace:%s, server response:%s" % (
                ip, port, service_name, self.namespace, c))
            return c.decode()
        except HTTPError as e:
            if e.code == HTTPStatus.FORBIDDEN:
                raise NacosException("Insufficient privilege.")
            else:
                raise NacosException("Request Error, code is %s" % e.code)
        except Exception as e:
            logger.exception("[modify-naming-instance] exception %s occur" % str(e))
            raise

使用示例

from pyfsops.nacos_api import NacosApi

server_addresses = ','.join(config.NACOS_INFO['address'])
client = NacosApi(server_addresses=server_addresses,
                  namespace=config.NACOS_INFO['namespace'],
                  username=config.NACOS_INFO['username'],
                  password=config.NACOS_INFO['password'])
instance_ip = tools.get_localhost_ip()


def register_nacos():
    rq = client.add_naming_instance(service_name="xxxxxxxxxxx", ip=instance_ip, port=18080,
                                    cluster_name="DEFAULT")
    rs = json.loads(rq)
    if rs['code'] != 0:
        logger.error(rs)


# 注册nacos
register_nacos()


def nacos_healthy():
    '''发送心跳'''
    rq = client.send_heartbeat(service_name="xxxxxxxxxxx", ip=instance_ip, port=18080,
                               cluster_name="DEFAULT")
    # logger.info(rq)
    if not rq['lightBeatEnabled']:
        logger.error(rq)

在manage.py里可以添加计划任务维持心跳

f = open("scheduler.lock", "wb")
try:
    # 为了保证任务只执行一次 需要添加文件锁
    fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
    scheduler = APScheduler()
    scheduler.init_app(app)
    scheduler.add_job(func=nacos_healthy, trigger='interval', seconds=15, id="xxxxxxxxxxx", timezone='Asia/Shanghai')

    scheduler.start()
except Exception as e:
    print(e)

posted @ 2025-09-26 12:12  醒日是归时  阅读(15)  评论(0)    收藏  举报