Commit 15dae3fd authored by 张宇's avatar 张宇

add security monitor

parent 86df2634
# -*- coding: utf-8 -*-
import logging
from gm_logging.utils import get_exception_logging_func
from raven.contrib.django.raven_compat.models import client as _sentry_client
info_logger = logging.getLogger('info_logger')
exception_logger = logging.getLogger('exception_logger')
logging_exception = get_exception_logging_func(exception_logger, _sentry_client)
\ No newline at end of file
# -*- coding: utf-8 -*-
from gm_rpcd.all import bind, context
ROUTER_PREFIX = 'courier/'
bind_prefix = lambda endpoint, **options: bind(ROUTER_PREFIX+endpoint, **options)
...@@ -646,3 +646,25 @@ class Message(models.Model): ...@@ -646,3 +646,25 @@ class Message(models.Model):
else: else:
text = u'' text = u''
return text return text
class MessageBlackUser(models.Model):
class Meta:
verbose_name = u'消息用户黑名单'
verbose_name_plural = u'消息用户黑名单'
app_label = 'api'
user_id = models.IntegerField(verbose_name=u'用户')
content = models.TextField(verbose_name=u'那条消息发生的错误', max_length=1024, default='', blank=True)
created_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now)
def __unicode__(self):
return u'用户【{}】于 {} 发送【{}】被限制'.format(self.user_id, self.created_time, self.content)
@property
def black_detail(self):
return {
'created_time': self.created_time.strftime('%Y-%m-%d'),
'content': self.content,
'user': self.user.last_name,
}
\ No newline at end of file
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from gm_types.hestia import MESSAGE_TYPE
SPECIAL_MSG_TYPE = frozenset([MESSAGE_TYPE.SERVICE, MESSAGE_TYPE.DOCTOR_TOPIC, MESSAGE_TYPE.TEXT_WITH_URL,
MESSAGE_TYPE.DIARY, MESSAGE_TYPE.GIFT])
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
...@@ -79,11 +79,23 @@ WSGI_APPLICATION = 'courier.wsgi.application' ...@@ -79,11 +79,23 @@ WSGI_APPLICATION = 'courier.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'zhengxing_test', 'NAME': 'whisper_test',
'USER': 'work', 'USER': 'work',
'PASSWORD': 'Gengmei1', 'PASSWORD': 'Gengmei1',
'HOST': 'mysql-server', 'HOST': 'bj-cdb-6slgqwlc.sql.tencentcdb.com',
'PORT': '3306', 'PORT': '62120',
'OPTIONS': {
"init_command": "SET foreign_key_checks = 0;",
"charset": "utf8mb4",
},
},
'slave': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'whisper_test',
'USER': 'work',
'PASSWORD': 'Gengmei1',
'HOST': 'bj-cdb-6slgqwlc.sql.tencentcdb.com',
'PORT': '62120',
'OPTIONS': { 'OPTIONS': {
"init_command": "SET foreign_key_checks = 0;", "init_command": "SET foreign_key_checks = 0;",
"charset": "utf8mb4", "charset": "utf8mb4",
...@@ -137,13 +149,13 @@ STATIC_URL = '/static/' ...@@ -137,13 +149,13 @@ STATIC_URL = '/static/'
####################################################### #######################################################
# https://docs.djangoproject.com/en/2.2/topics/logging/ # https://docs.djangoproject.com/en/2.2/topics/logging/
# django.utils.log:18, django.conf.global_settings:559 # django.utils.log:18, django.conf.global_settings:559
DJANGO_LOG_PATH = '/data/log/courier/app' PROJECT_LOG_PATH = '/data/log/courier/app'
if not os.path.exists(DJANGO_LOG_PATH): if not os.path.exists(PROJECT_LOG_PATH):
try: try:
os.makedirs(DJANGO_LOG_PATH, exist_ok=True) os.makedirs(PROJECT_LOG_PATH, exist_ok=True)
except PermissionError: except PermissionError:
DJANGO_LOG_PATH = os.path.join(BASE_DIR, 'log') PROJECT_LOG_PATH = os.path.join(BASE_DIR, 'log')
os.makedirs(DJANGO_LOG_PATH, exist_ok=True) os.makedirs(PROJECT_LOG_PATH, exist_ok=True)
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
...@@ -157,11 +169,15 @@ LOGGING = { ...@@ -157,11 +169,15 @@ LOGGING = {
}, },
}, },
'formatters': { 'formatters': {
"verbose": { 'verbose': {
"format": "[%(asctime)-23s]:%(levelname)s:@%(pathname)s:%(lineno)d, " "format": "[%(asctime)-23s]:%(levelname)s:@%(pathname)s:%(lineno)d, "
"%(funcName)s: %(message)s", "%(funcName)s: %(message)s",
# "datefmt": "%Y-%m-%d %H:%M:%S.%f%z" # "datefmt": "%Y-%m-%d %H:%M:%S.%f%z"
} },
'raw': {
'format': '%(message)s',
},
}, },
'handlers': { 'handlers': {
'console': { 'console': {
...@@ -173,7 +189,7 @@ LOGGING = { ...@@ -173,7 +189,7 @@ LOGGING = {
'django': { 'django': {
'level': 'INFO', 'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler', 'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(DJANGO_LOG_PATH, 'django.log'), 'filename': os.path.join(PROJECT_LOG_PATH, 'django.log'),
'when': 'midnight', 'when': 'midnight',
'interval': 1, 'interval': 1,
'backupCount': 365, 'backupCount': 365,
...@@ -182,12 +198,24 @@ LOGGING = { ...@@ -182,12 +198,24 @@ LOGGING = {
'default': { 'default': {
'level': 'DEBUG', 'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler', 'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(DJANGO_LOG_PATH, 'default.log'), 'filename': os.path.join(PROJECT_LOG_PATH, 'default.log'),
'when': 'midnight', 'when': 'midnight',
'interval': 1, 'interval': 1,
'backupCount': 365, 'backupCount': 365,
'formatter': 'verbose' 'formatter': 'verbose'
} },
'exception_logger_handler': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(PROJECT_LOG_PATH, 'exception_logger.log'),
'formatter': 'raw',
},
'info_logger_handler': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(PROJECT_LOG_PATH, 'info_logger.log'),
'formatter': 'verbose',
},
}, },
'loggers': { 'loggers': {
'': { '': {
...@@ -204,6 +232,17 @@ LOGGING = { ...@@ -204,6 +232,17 @@ LOGGING = {
'level': 'DEBUG' if DEBUG else 'WARNING', 'level': 'DEBUG' if DEBUG else 'WARNING',
'propagate': False 'propagate': False
}, },
'exception_logger': {
'handlers': ['exception_logger_handler'],
'level': 'INFO',
'propagate': False,
},
'info_logger': {
'handlers': ['info_logger_handler'],
'level': 'INFO',
'propagate': False,
},
# debug # debug
'kafka': { 'kafka': {
'level': 'FATAL' 'level': 'FATAL'
...@@ -234,5 +273,5 @@ except ModuleNotFoundError: ...@@ -234,5 +273,5 @@ except ModuleNotFoundError:
COUNT_LIMIT = 100 COUNT_LIMIT = 100
ES_SEARCH_TIMEOUT = '10s' ES_SEARCH_TIMEOUT = '10s'
DATABASE_ROUTERS = ['courier.db_routers.MessageRouter'] DATABASE_ROUTERS = ['courier.db_routers.MessageRouter']
MESSAGE_DB_NAME = 'message' MESSAGE_DB_NAME = 'default'
import multiprocessing import multiprocessing
workers = multiprocessing.cpu_count() + 1 workers = multiprocessing.cpu_count() + 1
bind = '0.0.0.0:8005' bind = '0.0.0.0:8000'
proc_name = 'courier' proc_name = 'courier'
timeout = 3600 timeout = 3600
preload_app = True preload_app = True
......
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from gm_types.utils.enum import Enum, unique
class CourierError(int, Enum):
pass
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import signal_handlers # fire sigal regist from . import signal_handlers # fire sigal regist
...@@ -4,19 +4,16 @@ from typing import List, Dict, Optional ...@@ -4,19 +4,16 @@ from typing import List, Dict, Optional
from six import string_types from six import string_types
import dateutil import dateutil
from django.db.models import Q from django.db.models import Q
from gm_rpcd.all import bind, context
from gm_types.gaia import MESSAGE_ORDER_TYPE from gm_types.gaia import MESSAGE_ORDER_TYPE
from gm_types.msg import CONVERSATION_TYPE, CONVERSATION_ORDER from gm_types.msg import CONVERSATION_TYPE, CONVERSATION_ORDER
from adapter.old_system import bind_prefix
from adapter.rpcd.exceptions import RPCPermanentError from adapter.rpcd.exceptions import RPCPermanentError
from api.models.message import ConversationUserStatus from api.models.message import ConversationUserStatus
from rpc import gaia_client from rpc import gaia_client
from search.utils import search_conversation_from_es from search.utils import search_conversation_from_es
from services.unread.stat import UserUnread from services.unread.stat import UserUnread
ROUTER_PREFIX = 'courier/'
bind_prefix = lambda endpoint, **options: bind(ROUTER_PREFIX+endpoint, **options)
@bind_prefix('message/conversation/list_v2') @bind_prefix('message/conversation/list_v2')
def batch_get_conversations_v2(user_ids: List[int], def batch_get_conversations_v2(user_ids: List[int],
...@@ -139,6 +136,7 @@ def batch_get_conversations_v2(user_ids: List[int], ...@@ -139,6 +136,7 @@ def batch_get_conversations_v2(user_ids: List[int],
'total_count': total_count, 'total_count': total_count,
} }
@bind_prefix('message/conversation/list_v3') @bind_prefix('message/conversation/list_v3')
def message_conversation_list_v3(user_ids: List[int], def message_conversation_list_v3(user_ids: List[int],
offset: int, offset: int,
...@@ -150,7 +148,7 @@ def message_conversation_list_v3(user_ids: List[int], ...@@ -150,7 +148,7 @@ def message_conversation_list_v3(user_ids: List[int],
is_star: Optional[bool]=None, is_star: Optional[bool]=None,
user_id: Optional[bool]=None, user_id: Optional[bool]=None,
user_last_name: Optional[bool]=None, user_last_name: Optional[bool]=None,
comment: Optional[bool]=None) -> dict: comment: Optional[bool]=None) -> Dict:
""" """
获取会话列表, 与v2不同的是全部走es获取conversation_ids 获取会话列表, 与v2不同的是全部走es获取conversation_ids
:param user_ids: LIST[USER_ID] :param user_ids: LIST[USER_ID]
...@@ -204,8 +202,6 @@ def message_conversation_list_v3(user_ids: List[int], ...@@ -204,8 +202,6 @@ def message_conversation_list_v3(user_ids: List[int],
} }
es_result_not_reply = search_conversation_from_es(offset=offset, size=size, filters=es_filters, es_result_not_reply = search_conversation_from_es(offset=offset, size=size, filters=es_filters,
query=es_query, sort_type=es_sort_type) query=es_query, sort_type=es_sort_type)
# es_result = search_conversation_from_es(offset=offset, size=size, filters=es_filters,
# query=es_query, sort_type=es_sort_type)
if reply_status == None: if reply_status == None:
es_result = es_result_total es_result = es_result_total
else: else:
...@@ -234,6 +230,5 @@ def message_conversation_list_v3(user_ids: List[int], ...@@ -234,6 +230,5 @@ def message_conversation_list_v3(user_ids: List[int],
'reply_total': es_result_reply['total_count'], 'reply_total': es_result_reply['total_count'],
'not_reply_total': es_result_not_reply['total_count'] 'not_reply_total': es_result_not_reply['total_count']
} }
pass
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from gm_types.gaia import DOCTOR_TYPE
from adapter.old_system import bind_prefix
from api.models.message import Message, ConversationUserStatus
from common.types import SPECIAL_MSG_TYPE
from rpc.gaia_client import batch_get_doctor_info_by_user_ids
@bind_prefix('message/message/message_monitor')
def message_monitor(ctx, msg_ids):
""" 异常私信监控对backend暴露接口 """
ret = []
user_id_list = []
messages = Message.objects.filter(id__in=msg_ids)
for message in messages:
if message.type in SPECIAL_MSG_TYPE:
content = Message.get_special_msg_content(message.type, message.body)
else:
content = Message.get_general_msg_content(message.type, message.content)
cs = ConversationUserStatus.objects.filter(
conversation_id=message.conversation_id
).exclude(user_id=message.user_id).first()
if not cs:
continue
target_uid = cs.user_id
user_id_list.append(target_uid)
ret.append({
"send_uid": message.user_id,
"content": content,
"send_time": message.send_time.strftime('%Y-%m-%d %H:%M:%S'),
"conversation_id": message.conversation_id,
'target_uid': target_uid,
})
user_id_doctor_info_dict = batch_get_doctor_info_by_user_ids(user_ids=user_id_list)
for i in ret:
doctor_info = user_id_doctor_info_dict.get(str(i.get('target_uid')))
if not doctor_info:
i.update(doctor_name='',
doctor_id='',
hospital_name='')
else:
doctor_name = doctor_info.get('meta', {}).get('name', '')
if doctor_name:
doctor_type = doctor_info.get('meta', {}).get('doctor_type')
if doctor_type != DOCTOR_TYPE.OFFICER:
doctor_name = '{} 医生'.format(doctor_name)
i.update(
doctor_name=doctor_name,
doctor_id=doctor_info.get('meta', {}).get('id', ''),
hospital_name=doctor_info.get('meta', {}).get('hospital_name', '')
)
return ret
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import functools
from typing import Union, Dict, List
import helios
from gm_types.error import ERROR
from adapter.log import logging_exception
def rpc_invoke_error_handle_decorator(invoke_func) -> (bool, Union[Dict, List]):
@functools.wraps(invoke_func)
def wrapper(*args, **kwargs):
try:
result = invoke_func(*args, **kwargs)
except helios.rpc.internal.exceptions.RPCFaultException as e:
# errors like lack of parameter, need logined .etc
# url = e.parse_url(e.rpcd_server.v1_batch_url, e.request.method)
# formated_message = e.kwargs.get('message', '')
# url, e.request.params, e.request.session_key,
logging_exception()
return False, {'code':e.error, 'msg': e.message}
except helios.rpc.internal.exceptions.RPCSystemException as e:
# errors like attribute errror, unpack dict error .etc
# url = e.parse_url(e.rpcd_server.v1_batch_url, e.request.method)
# formated_message = e.kwargs.get('message', '')
# print(formated_message) # Server Error: argument of type 'NoneType' is not iterable
# print(url, e.request.params, e.request.session_key)
logging_exception()
return False, {'code': ERROR.UNIVERSAL, 'msg': ERROR.getDesc(ERROR.UNIVERSAL)}
else:
return True, result
return wrapper
\ No newline at end of file
import contextlib import contextlib
import logging
import threading import threading
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.conf import settings from django.conf import settings
from cached_property import cached_property from cached_property import cached_property
from gm_logging.utils import get_exception_logging_func
from helios.rpc import create_default_invoker from helios.rpc import create_default_invoker
from adapter.log import info_logger, logging_exception
from adapter.rpcd.exceptions import RPCLoginRequiredException from adapter.rpcd.exceptions import RPCLoginRequiredException
from . import auth from . import auth
# from .tool.log_tool import logging_exception, info_logger
info_logger = logging.getLogger(__name__)
exception_logger = logging.getLogger('exception_logger')
from raven.contrib.django.raven_compat.models import client as _sentry_client
logging_exception = get_exception_logging_func(exception_logger, _sentry_client)
class Session(object): class Session(object):
...@@ -45,11 +38,11 @@ class Session(object): ...@@ -45,11 +38,11 @@ class Session(object):
@property @property
def has_login(self): def has_login(self):
user = self.user user = self.user
info_logger.info(user) info_logger.info(user)
res = user.is_authenticated() res = user.is_authenticated()
return res return res
def login_required(self): def login_required(self):
if not self.has_login: if not self.has_login:
raise RPCLoginRequiredException raise RPCLoginRequiredException
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import List, Dict from typing import List, Dict, Optional, Set
from rpc import rpc_invoke_error_handle_decorator
from rpc.context import get_rpc_remote_invoker from rpc.context import get_rpc_remote_invoker
def get_doctor_id_by_user_id(user_id: int) -> int: @rpc_invoke_error_handle_decorator
result = get_rpc_remote_invoker()['gaia/get_doctor_id_by_user_id']( def get_doctor_info(user_id: str) -> Dict:
return get_rpc_remote_invoker()['api/doctor'](
user_id=user_id, user_id=user_id,
).unwrap() ).unwrap()
return result.get('user_id')
@rpc_invoke_error_handle_decorator
def batch_get_doctor_info_by_doctor_ids(doctor_ids, with_fields=None) -> List[Dict]:
return get_rpc_remote_invoker()['api/batch_get_doctor_info_by_doctor_ids'](
doctor_ids=doctor_ids,
with_fields=with_fields
).unwrap()
def get_message_mark_user(user_id_list: List[int], def get_message_mark_user(user_id_list: List[int],
...@@ -18,3 +27,25 @@ def get_message_mark_user(user_id_list: List[int], ...@@ -18,3 +27,25 @@ def get_message_mark_user(user_id_list: List[int],
user_id_list=user_id_list, user_id_list=user_id_list,
target_user_id_list=target_user_id_list target_user_id_list=target_user_id_list
).unwrap() ).unwrap()
@rpc_invoke_error_handle_decorator
def get_toc_hospital_basic_info(hospital_id: str,
with_fields: Optional[List[str]]=['meta']):
return get_rpc_remote_invoker()['api/get_toc_hospital_basic_info'](
hospital_id=hospital_id,
with_fields=with_fields
).unwrap()
def get_doctor_basic_info(doctor_ids: List[str]) -> List[Dict]:
return get_rpc_remote_invoker()['api/doctor/basic_info'](
doctor_ids=doctor_ids
).unwrap()
def batch_get_doctor_info_by_user_ids(user_ids: List[int]) -> Dict:
return get_rpc_remote_invoker()['api/batch_get_doctor_info_by_user_ids'](
user_ids=user_ids,
with_fields=['meta']
).unwrap()
...@@ -8,12 +8,12 @@ from gm_types.msg import CONVERSATION_TYPE, CONVERSATION_ORDER ...@@ -8,12 +8,12 @@ from gm_types.msg import CONVERSATION_TYPE, CONVERSATION_ORDER
limited_size = functools.partial(min, settings.COUNT_LIMIT) limited_size = functools.partial(min, settings.COUNT_LIMIT)
def search_conversation_from_es(offset=0, def search_conversation_from_es(offset: int=0,
size=50, size: int=50,
filters={}, filters: Dict={},
query={}, query: Dict={},
sort_type=CONVERSATION_ORDER.LAST_REPLY_TIME)\ sort_type: CONVERSATION_ORDER=CONVERSATION_ORDER.LAST_REPLY_TIME)\
-> dict: -> Dict:
res = search_conversation_es(offset, size, filters, query, sort_type) res = search_conversation_es(offset, size, filters, query, sort_type)
conversation_ids = [int(s['_id']) for s in res['hits']['hits']] conversation_ids = [int(s['_id']) for s in res['hits']['hits']]
total_count = res['hits']['total'] total_count = res['hits']['total']
...@@ -22,11 +22,12 @@ def search_conversation_from_es(offset=0, ...@@ -22,11 +22,12 @@ def search_conversation_from_es(offset=0,
'conversation_ids': conversation_ids, 'conversation_ids': conversation_ids,
} }
def search_conversation_es(offset: int=0, def search_conversation_es(offset: int=0,
size: int=50, size: int=50,
filters: Dict={}, filters: Dict={},
query: Dict={}, query: Dict={},
sort_type:CONVERSATION_ORDER=CONVERSATION_ORDER.LAST_REPLY_TIME): sort_type: CONVERSATION_ORDER=CONVERSATION_ORDER.LAST_REPLY_TIME):
size = limited_size(size) size = limited_size(size)
filter_element_list = [] filter_element_list = []
......
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from adapter.old_system import bind_prefix
from api.models.message import MessageBlackUser
@bind_prefix('message/message/block_message')
def block_message(ctx, user_id):
""" 手动拉黑私信功能 (内部使用) """
MessageBlackUser.objects.create(user_id=user_id)
return {"status": True}
\ No newline at end of file
...@@ -5,7 +5,7 @@ import datetime ...@@ -5,7 +5,7 @@ import datetime
from django.conf import settings from django.conf import settings
# from api.models import Doctor, Order, RefundOrder, Reservation # from api.models import Doctor, Order, RefundOrder, Reservation
from django.core.cache import cache
from rpc.cache import unread_cache from rpc.cache import unread_cache
# from rpc.tool.log_tool import doctor_unread_logger # from rpc.tool.log_tool import doctor_unread_logger
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment