完善权限
This commit is contained in:
commit
a90d31c420
BIN
data_backup.json
Normal file
BIN
data_backup.json
Normal file
Binary file not shown.
0
feishu/__init__.py
Normal file
0
feishu/__init__.py
Normal file
184
feishu/feishu.py
Normal file
184
feishu/feishu.py
Normal file
@ -0,0 +1,184 @@
|
||||
import json
|
||||
import django
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 设置 Django 环境
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'role_based_system.settings')
|
||||
django.setup()
|
||||
|
||||
import lark_oapi as lark
|
||||
from lark_oapi.api.bitable.v1 import *
|
||||
from user_management.models import FeishuCreator
|
||||
|
||||
|
||||
# SDK 使用说明: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/python--sdk/preparations-before-development
|
||||
# 以下示例代码默认根据文档示例值填充,如果存在代码问题,请在 API 调试台填上相关必要参数后再复制代码使用
|
||||
def extract_field_value(field_value):
|
||||
"""提取字段值"""
|
||||
if isinstance(field_value, list):
|
||||
if field_value and isinstance(field_value[0], dict):
|
||||
return field_value[0].get('text', '')
|
||||
elif isinstance(field_value, dict):
|
||||
if 'text' in field_value:
|
||||
return field_value['text']
|
||||
elif 'link' in field_value:
|
||||
return field_value['link']
|
||||
elif 'link_record_ids' in field_value:
|
||||
return ''
|
||||
return field_value
|
||||
|
||||
def save_to_database(record):
|
||||
"""保存记录到数据库"""
|
||||
fields = record.fields
|
||||
record_id = record.record_id
|
||||
|
||||
creator_data = {
|
||||
'record_id': record_id,
|
||||
'contact_person': extract_field_value(fields.get('对接人', '')),
|
||||
'handle': extract_field_value(fields.get('Handle', '')),
|
||||
'tiktok_url': extract_field_value(fields.get('链接', '')),
|
||||
'fans_count': extract_field_value(fields.get('粉丝数', '')),
|
||||
'gmv': fields.get('GMV', ''),
|
||||
'email': extract_field_value(fields.get('邮箱', '')),
|
||||
'phone': extract_field_value(fields.get('手机号|WhatsApp', '')),
|
||||
'account_type': extract_field_value(fields.get('账号属性', [])),
|
||||
'price_quote': fields.get('报价', ''),
|
||||
'response_speed': fields.get('回复速度', ''),
|
||||
'cooperation_intention': fields.get('合作意向', ''),
|
||||
'payment_method': fields.get('支付方式', ''),
|
||||
'payment_account': fields.get('收款账号', ''),
|
||||
'address': fields.get('收件地址', ''),
|
||||
'has_ooin': fields.get('签约OOIN?', ''),
|
||||
'source': fields.get('渠道来源', ''),
|
||||
'contact_status': fields.get('建联进度', ''),
|
||||
'cooperation_brands': fields.get('合作品牌', []),
|
||||
'system_categories': extract_field_value(fields.get('系统展示的带货品类', [])),
|
||||
'actual_categories': extract_field_value(fields.get('实际高播放量带货品类', [])),
|
||||
'human_categories': fields.get('达人标想要货品类', ''),
|
||||
'creator_base': '', # 如果有这个字段,添加相应的处理
|
||||
'notes': extract_field_value(fields.get('父记录', '')),
|
||||
}
|
||||
|
||||
try:
|
||||
creator, created = FeishuCreator.objects.update_or_create(
|
||||
record_id=record_id,
|
||||
defaults=creator_data
|
||||
)
|
||||
return creator, created
|
||||
except Exception as e:
|
||||
print(f"保存记录时出错: {str(e)}")
|
||||
print(f"记录数据: {creator_data}")
|
||||
return None, False
|
||||
|
||||
def fetch_all_records(client, app_token, table_id, user_access_token):
|
||||
"""获取所有记录"""
|
||||
total_records = []
|
||||
page_token = None
|
||||
page_size = 20
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 构造请求对象
|
||||
builder = SearchAppTableRecordRequest.builder() \
|
||||
.app_token(app_token) \
|
||||
.table_id(table_id) \
|
||||
.page_size(page_size)
|
||||
|
||||
# 如果有page_token,添加到请求中
|
||||
if page_token:
|
||||
builder = builder.page_token(page_token)
|
||||
|
||||
# 构建完整请求
|
||||
request = builder.request_body(SearchAppTableRecordRequestBody.builder().build()).build()
|
||||
|
||||
print(f"发送请求,page_token: {page_token}")
|
||||
|
||||
# 发起请求
|
||||
option = lark.RequestOption.builder().user_access_token(user_access_token).build()
|
||||
response = client.bitable.v1.app_table_record.search(request, option)
|
||||
|
||||
if not response.success():
|
||||
print(f"请求失败: {response.code}, {response.msg}")
|
||||
break
|
||||
|
||||
# 获取当前页记录
|
||||
current_records = response.data.items
|
||||
if not current_records:
|
||||
print("没有更多记录")
|
||||
break
|
||||
|
||||
total_records.extend(current_records)
|
||||
|
||||
# 解析响应数据获取分页信息
|
||||
response_data = json.loads(response.raw.content)
|
||||
total = response_data["data"]["total"]
|
||||
print(f"获取到 {len(current_records)} 条记录,当前总计: {len(total_records)}/{total} 条")
|
||||
|
||||
# 获取下一页token
|
||||
page_token = response_data["data"].get("page_token")
|
||||
if not page_token or not response_data["data"].get("has_more", False):
|
||||
print("已获取所有数据")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"错误: {str(e)}")
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
break
|
||||
|
||||
print(f"最终获取到 {len(total_records)} 条记录")
|
||||
return total_records
|
||||
|
||||
def main():
|
||||
# 创建client
|
||||
client = lark.Client.builder() \
|
||||
.enable_set_token(True) \
|
||||
.log_level(lark.LogLevel.DEBUG) \
|
||||
.build()
|
||||
|
||||
# 配置参数
|
||||
APP_TOKEN = "XYE6bMQUOaZ5y5svj4vcWohGnmg"
|
||||
TABLE_ID = "tbl3oikG3F8YYtVA"
|
||||
USER_ACCESS_TOKEN = "u-ecM5BmzKx4uHz3sG0FouQSk1l9kxgl_3Xa00l5Ma24Jy"
|
||||
|
||||
# 获取所有记录
|
||||
print("开始获取所有记录...")
|
||||
all_records = fetch_all_records(client, APP_TOKEN, TABLE_ID, USER_ACCESS_TOKEN)
|
||||
|
||||
if not all_records:
|
||||
print("未获取到任何记录")
|
||||
return
|
||||
|
||||
# 更新数据库
|
||||
print("\n开始更新数据库...")
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
error_count = 0
|
||||
|
||||
for record in all_records:
|
||||
creator, created = save_to_database(record)
|
||||
if creator:
|
||||
if created:
|
||||
created_count += 1
|
||||
if created_count % 10 == 0: # 每10条才打印一次,避免输出过多
|
||||
print(f"已创建 {created_count} 条记录...")
|
||||
else:
|
||||
updated_count += 1
|
||||
if updated_count % 10 == 0:
|
||||
print(f"已更新 {updated_count} 条记录...")
|
||||
else:
|
||||
error_count += 1
|
||||
print(f"处理记录失败")
|
||||
|
||||
# 打印统计信息
|
||||
print("\n更新完成!统计信息:")
|
||||
print(f"新建记录:{created_count}")
|
||||
print(f"更新记录:{updated_count}")
|
||||
print(f"错误记录:{error_count}")
|
||||
print(f"总记录数:{len(all_records)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
1
gmail/client_secret.json
Normal file
1
gmail/client_secret.json
Normal file
@ -0,0 +1 @@
|
||||
{"installed":{"client_id":"240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com","project_id":"first-renderer-454910-c1","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}
|
20
gmail/email_conversations.json
Normal file
20
gmail/email_conversations.json
Normal file
@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"subject": "这是主题",
|
||||
"from": "crush wds <ardonisierni@gmail.com>",
|
||||
"date": "2025-03-27 12:04:29",
|
||||
"body": "你好呀\r\n"
|
||||
},
|
||||
{
|
||||
"subject": "",
|
||||
"from": "wds crush <crushwds@gmail.com>",
|
||||
"date": "2025-03-27 12:05:54",
|
||||
"body": "你那里天气怎么样\r\n"
|
||||
},
|
||||
{
|
||||
"subject": "",
|
||||
"from": "wds crush <crushwds@gmail.com>",
|
||||
"date": "2025-03-27 13:13:03",
|
||||
"body": "吃饭了吗\r\n"
|
||||
}
|
||||
]
|
25
gmail/email_conversations.txt
Normal file
25
gmail/email_conversations.txt
Normal file
@ -0,0 +1,25 @@
|
||||
==================================================
|
||||
记录时间: 2025-03-27 13:15:18
|
||||
==================================================
|
||||
|
||||
时间: 2025-03-27 12:04:29
|
||||
发件人: crush wds <ardonisierni@gmail.com>
|
||||
主题: 这是主题
|
||||
内容:
|
||||
你好呀
|
||||
|
||||
--------------------------------------------------
|
||||
时间: 2025-03-27 12:05:54
|
||||
发件人: wds crush <crushwds@gmail.com>
|
||||
主题:
|
||||
内容:
|
||||
你那里天气怎么样
|
||||
|
||||
--------------------------------------------------
|
||||
时间: 2025-03-27 13:13:03
|
||||
发件人: wds crush <crushwds@gmail.com>
|
||||
主题:
|
||||
内容:
|
||||
吃饭了吗
|
||||
|
||||
--------------------------------------------------
|
146
gmail/gmail_read.py
Normal file
146
gmail/gmail_read.py
Normal file
@ -0,0 +1,146 @@
|
||||
'''
|
||||
Reading GMAIL using Python
|
||||
- Abhishek Chhibber
|
||||
'''
|
||||
|
||||
'''
|
||||
This script does the following:
|
||||
- Go to Gmal inbox
|
||||
- Find and read all the unread messages
|
||||
- Extract details (Date, Sender, Subject, Snippet, Body) and export them to a .csv file / DB
|
||||
- Mark the messages as Read - so that they are not read again
|
||||
'''
|
||||
|
||||
'''
|
||||
Before running this script, the user should get the authentication by following
|
||||
the link: https://developers.google.com/gmail/api/quickstart/python
|
||||
Also, credentials.json should be saved in the same directory as this file
|
||||
'''
|
||||
|
||||
# Importing required libraries
|
||||
from apiclient import discovery
|
||||
from apiclient import errors
|
||||
from httplib2 import Http
|
||||
from oauth2client import file, client, tools
|
||||
import base64
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
import time
|
||||
import dateutil.parser as parser
|
||||
from datetime import datetime
|
||||
import datetime
|
||||
import csv
|
||||
|
||||
|
||||
# Creating a storage.JSON file with authentication details
|
||||
SCOPES = 'https://www.googleapis.com/auth/gmail.modify' # we are using modify and not readonly, as we will be marking the messages Read
|
||||
store = file.Storage('storage.json')
|
||||
creds = store.get()
|
||||
if not creds or creds.invalid:
|
||||
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
|
||||
creds = tools.run_flow(flow, store)
|
||||
GMAIL = discovery.build('gmail', 'v1', http=creds.authorize(Http()))
|
||||
|
||||
user_id = 'me'
|
||||
label_id_one = 'INBOX'
|
||||
label_id_two = 'UNREAD'
|
||||
|
||||
# Getting all the unread messages from Inbox
|
||||
# labelIds can be changed accordingly
|
||||
unread_msgs = GMAIL.users().messages().list(userId='me',labelIds=[label_id_one, label_id_two]).execute()
|
||||
|
||||
# We get a dictonary. Now reading values for the key 'messages'
|
||||
mssg_list = unread_msgs['messages']
|
||||
|
||||
print ("Total unread messages in inbox: ", str(len(mssg_list)))
|
||||
|
||||
final_list = [ ]
|
||||
|
||||
|
||||
for mssg in mssg_list:
|
||||
temp_dict = { }
|
||||
m_id = mssg['id'] # get id of individual message
|
||||
message = GMAIL.users().messages().get(userId=user_id, id=m_id).execute() # fetch the message using API
|
||||
payld = message['payload'] # get payload of the message
|
||||
headr = payld['headers'] # get header of the payload
|
||||
|
||||
|
||||
for one in headr: # getting the Subject
|
||||
if one['name'] == 'Subject':
|
||||
msg_subject = one['value']
|
||||
temp_dict['Subject'] = msg_subject
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
for two in headr: # getting the date
|
||||
if two['name'] == 'Date':
|
||||
msg_date = two['value']
|
||||
date_parse = (parser.parse(msg_date))
|
||||
m_date = (date_parse.date())
|
||||
temp_dict['Date'] = str(m_date)
|
||||
else:
|
||||
pass
|
||||
|
||||
for three in headr: # getting the Sender
|
||||
if three['name'] == 'From':
|
||||
msg_from = three['value']
|
||||
temp_dict['Sender'] = msg_from
|
||||
else:
|
||||
pass
|
||||
|
||||
temp_dict['Snippet'] = message['snippet'] # fetching message snippet
|
||||
|
||||
|
||||
try:
|
||||
|
||||
# Fetching message body
|
||||
mssg_parts = payld['parts'] # fetching the message parts
|
||||
part_one = mssg_parts[0] # fetching first element of the part
|
||||
part_body = part_one['body'] # fetching body of the message
|
||||
part_data = part_body['data'] # fetching data from the body
|
||||
clean_one = part_data.replace("-","+") # decoding from Base64 to UTF-8
|
||||
clean_one = clean_one.replace("_","/") # decoding from Base64 to UTF-8
|
||||
clean_two = base64.b64decode (bytes(clean_one, 'UTF-8')) # decoding from Base64 to UTF-8
|
||||
soup = BeautifulSoup(clean_two , "lxml" )
|
||||
mssg_body = soup.body()
|
||||
# mssg_body is a readible form of message body
|
||||
# depending on the end user's requirements, it can be further cleaned
|
||||
# using regex, beautiful soup, or any other method
|
||||
temp_dict['Message_body'] = mssg_body
|
||||
|
||||
except :
|
||||
pass
|
||||
|
||||
print (temp_dict)
|
||||
final_list.append(temp_dict) # This will create a dictonary item in the final list
|
||||
|
||||
# This will mark the messagea as read
|
||||
GMAIL.users().messages().modify(userId=user_id, id=m_id,body={ 'removeLabelIds': ['UNREAD']}).execute()
|
||||
|
||||
|
||||
|
||||
|
||||
print ("Total messaged retrived: ", str(len(final_list)))
|
||||
|
||||
'''
|
||||
|
||||
The final_list will have dictionary in the following format:
|
||||
|
||||
{ 'Sender': '"email.com" <name@email.com>',
|
||||
'Subject': 'Lorem ipsum dolor sit ametLorem ipsum dolor sit amet',
|
||||
'Date': 'yyyy-mm-dd',
|
||||
'Snippet': 'Lorem ipsum dolor sit amet'
|
||||
'Message_body': 'Lorem ipsum dolor sit amet'}
|
||||
|
||||
|
||||
The dictionary can be exported as a .csv or into a databse
|
||||
'''
|
||||
|
||||
#exporting the values as .csv
|
||||
with open('CSV_NAME.csv', 'w', encoding='utf-8', newline = '') as csvfile:
|
||||
fieldnames = ['Sender','Subject','Date','Snippet','Message_body']
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter = ',')
|
||||
writer.writeheader()
|
||||
for val in final_list:
|
||||
writer.writerow(val)
|
171
gmail/quickstart.py
Normal file
171
gmail/quickstart.py
Normal file
@ -0,0 +1,171 @@
|
||||
from apiclient import discovery
|
||||
from httplib2 import Http
|
||||
from oauth2client import file, client, tools
|
||||
import base64
|
||||
from bs4 import BeautifulSoup
|
||||
import dateutil.parser as parser
|
||||
from datetime import datetime
|
||||
import os
|
||||
import json
|
||||
|
||||
# 代理设置
|
||||
os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
|
||||
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
|
||||
|
||||
# Gmail API 认证
|
||||
SCOPES = 'https://www.googleapis.com/auth/gmail.readonly'
|
||||
store = file.Storage('storage.json')
|
||||
creds = store.get()
|
||||
if not creds or creds.invalid:
|
||||
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
|
||||
creds = tools.run_flow(flow, store)
|
||||
GMAIL = discovery.build('gmail', 'v1', http=creds.authorize(Http()))
|
||||
|
||||
def get_email_content(message):
|
||||
"""提取邮件内容"""
|
||||
try:
|
||||
payload = message['payload']
|
||||
headers = payload['headers']
|
||||
|
||||
# 获取邮件基本信息
|
||||
email_data = {
|
||||
'subject': '',
|
||||
'from': '',
|
||||
'date': '',
|
||||
'body': ''
|
||||
}
|
||||
|
||||
# 提取头部信息
|
||||
for header in headers:
|
||||
if header['name'] == 'Subject':
|
||||
email_data['subject'] = header['value']
|
||||
elif header['name'] == 'From':
|
||||
email_data['from'] = header['value']
|
||||
elif header['name'] == 'Date':
|
||||
date = parser.parse(header['value'])
|
||||
email_data['date'] = date.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 提取邮件正文
|
||||
if 'parts' in payload:
|
||||
parts = payload['parts']
|
||||
for part in parts:
|
||||
if part['mimeType'] == 'text/plain':
|
||||
data = part['body'].get('data', '')
|
||||
if data:
|
||||
text = base64.urlsafe_b64decode(data).decode('utf-8')
|
||||
email_data['body'] = text
|
||||
break
|
||||
elif 'body' in payload:
|
||||
data = payload['body'].get('data', '')
|
||||
if data:
|
||||
text = base64.urlsafe_b64decode(data).decode('utf-8')
|
||||
email_data['body'] = text
|
||||
|
||||
return email_data
|
||||
except Exception as e:
|
||||
print(f"Error processing email: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_conversations(email1, email2):
|
||||
"""获取两个用户之间的所有对话"""
|
||||
try:
|
||||
# 构建搜索查询
|
||||
query = f"from:({email1} OR {email2}) to:({email1} OR {email2})"
|
||||
|
||||
# 获取所有匹配的邮件
|
||||
response = GMAIL.users().messages().list(userId='me', q=query).execute()
|
||||
messages = []
|
||||
|
||||
if 'messages' in response:
|
||||
messages.extend(response['messages'])
|
||||
|
||||
# 如果有更多页,继续获取
|
||||
while 'nextPageToken' in response:
|
||||
page_token = response['nextPageToken']
|
||||
response = GMAIL.users().messages().list(
|
||||
userId='me',
|
||||
q=query,
|
||||
pageToken=page_token
|
||||
).execute()
|
||||
messages.extend(response['messages'])
|
||||
|
||||
# 获取每封邮件的详细内容
|
||||
conversations = []
|
||||
for msg in messages:
|
||||
message = GMAIL.users().messages().get(userId='me', id=msg['id']).execute()
|
||||
email_data = get_email_content(message)
|
||||
if email_data:
|
||||
conversations.append(email_data)
|
||||
|
||||
# 按时间排序
|
||||
conversations.sort(key=lambda x: x['date'])
|
||||
|
||||
return conversations
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting conversations: {str(e)}")
|
||||
return []
|
||||
|
||||
def save_conversations(conversations, output_file):
|
||||
"""保存对话记录(覆盖模式)"""
|
||||
try:
|
||||
# 使用 'w' 模式覆盖内容
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
# 写入时间分割线
|
||||
f.write("=" * 50 + "\n")
|
||||
f.write(f"记录时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
f.write("=" * 50 + "\n\n")
|
||||
|
||||
# 写入对话记录
|
||||
for msg in conversations:
|
||||
f.write(f"时间: {msg['date']}\n")
|
||||
f.write(f"发件人: {msg['from']}\n")
|
||||
f.write(f"主题: {msg['subject']}\n")
|
||||
f.write("内容:\n")
|
||||
f.write(f"{msg['body']}\n")
|
||||
f.write("-" * 50 + "\n")
|
||||
|
||||
print(f"对话记录已保存到: {output_file}")
|
||||
|
||||
# 保存 JSON 格式
|
||||
json_file = output_file.rsplit('.', 1)[0] + '.json'
|
||||
with open(json_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(conversations, f, ensure_ascii=False, indent=2)
|
||||
print(f"JSON 格式对话记录已保存到: {json_file}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error saving conversations: {str(e)}")
|
||||
|
||||
def main():
|
||||
# 设置固定的输出文件名,这样每次都会追加到同一个文件
|
||||
output_file = "email_conversations.txt"
|
||||
|
||||
# 设置要查找的两个邮箱地址
|
||||
email1 = "crushwds@gmail.com"
|
||||
email2 = "ardonisierni@gmail.com"
|
||||
|
||||
print(f"正在获取 {email1} 和 {email2} 之间的对话...")
|
||||
|
||||
# 获取对话记录
|
||||
conversations = get_conversations(email1, email2)
|
||||
|
||||
if conversations:
|
||||
print(f"找到 {len(conversations)} 条对话记录")
|
||||
|
||||
# 保存对话记录(追加模式)
|
||||
save_conversations(conversations, output_file)
|
||||
|
||||
# 打印对话统计
|
||||
print("\n对话统计:")
|
||||
print(f"总消息数: {len(conversations)}")
|
||||
senders = {}
|
||||
for msg in conversations:
|
||||
sender = msg['from']
|
||||
senders[sender] = senders.get(sender, 0) + 1
|
||||
for sender, count in senders.items():
|
||||
print(f"{sender}: {count} 条消息")
|
||||
else:
|
||||
print("未找到对话记录")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
3
gmail/requirements.txt
Normal file
3
gmail/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
google-api-python-client==1.7.8
|
||||
google-auth-httplib2==0.0.3
|
||||
google-auth-oauthlib==0.4.0
|
1
gmail/storage.json
Normal file
1
gmail/storage.json
Normal file
@ -0,0 +1 @@
|
||||
{"access_token": "ya29.a0AeXRPp7jfizXj6NfUavzABcDOEH-PD7TckkeCwQWFksLZwkFXqAaVe1gCeZZIWtP4hMD3F2-K7-hdSQAzCiyP7Z_72YPr_r3UDfflr6XOV7czJ937QknW_236kLJK1DDggpB4BFCB1cXtSJa6V4pbyez4lkka021mZWegOdGaCgYKATkSARISFQHGX2Mi7Ua3f90G-JLtvzu2YJgvoA0175", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "refresh_token": "1//0eUNnePCc4hEKCgYIARAAGA4SNwF-L9IrIRfxg1JwnMu0WHZAu-uCWT7cx1PncId3bPZr4x53Mo57xGXr2WjiF4szuSQQkeL4hNU", "token_expiry": "2025-03-27T06:13:12Z", "token_uri": "https://oauth2.googleapis.com/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.a0AeXRPp7jfizXj6NfUavzABcDOEH-PD7TckkeCwQWFksLZwkFXqAaVe1gCeZZIWtP4hMD3F2-K7-hdSQAzCiyP7Z_72YPr_r3UDfflr6XOV7czJ937QknW_236kLJK1DDggpB4BFCB1cXtSJa6V4pbyez4lkka021mZWegOdGaCgYKATkSARISFQHGX2Mi7Ua3f90G-JLtvzu2YJgvoA0175", "expires_in": 3599, "scope": "https://www.googleapis.com/auth/gmail.modify", "token_type": "Bearer", "refresh_token_expires_in": 599654}, "scopes": ["https://www.googleapis.com/auth/gmail.modify"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
|
1
gmail/test.txt
Normal file
1
gmail/test.txt
Normal file
@ -0,0 +1 @@
|
||||
你好
|
1
gmail/token.json
Normal file
1
gmail/token.json
Normal file
@ -0,0 +1 @@
|
||||
{"token": "ya29.a0AeXRPp5MvarP9U4JZ3ZlV_xpIeHNL7IK1HEQLX6qhY-thS5IOFsdHrcNwbdNqkYrClFm4yGiMCXzWO-IilVbdSrDfEDw-Qfs7V9oTsZS2RYjbgcSR9rLUtZlL8ObnaljQie5VVzQZysHPZcMSdWiqabcQq2UsU5TNuOWo4J7_waCgYKAQESARISFQHGX2MiAR1O_HNYd0F6z0Rj-vSybA0177", "refresh_token": "1//0e1Usc30XxlXRCgYIARAAGA4SNwF-L9IrOOjEN7EkOSatDMHzTy0KaNdp_jf1XtLn7a5pFSljo3IKnrDdvVnxTO_FX5Asj_UDiAw", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "240872828479-570luspoc31259l1faab6kmjmpcsa9n9.apps.googleusercontent.com", "client_secret": "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", "scopes": ["https://www.googleapis.com/auth/gmail.readonly"], "universe_domain": "googleapis.com", "account": "", "expiry": "2025-03-27T04:39:31.274041Z"}
|
@ -106,6 +106,14 @@ DATABASES = {
|
||||
'PASSWORD': '123456',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '3306',
|
||||
'OPTIONS': {
|
||||
'charset': 'utf8mb4', # 使用 utf8mb4 字符集
|
||||
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'; SET innodb_strict_mode=1; SET NAMES utf8mb4;",
|
||||
},
|
||||
'TEST': {
|
||||
'CHARSET': 'utf8mb4',
|
||||
'COLLATION': 'utf8mb4_unicode_ci',
|
||||
},
|
||||
}
|
||||
}
|
||||
# Password validation
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-26 09:23
|
||||
# Generated by Django 5.1.5 on 2025-03-28 06:51
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
@ -18,6 +18,43 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FeishuCreator',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('record_id', models.CharField(max_length=100, unique=True, verbose_name='飞书记录ID')),
|
||||
('contact_person', models.CharField(blank=True, max_length=50, verbose_name='对接人')),
|
||||
('handle', models.CharField(blank=True, max_length=100, verbose_name='Handle')),
|
||||
('tiktok_url', models.URLField(blank=True, verbose_name='链接')),
|
||||
('fans_count', models.CharField(blank=True, max_length=50, verbose_name='粉丝数')),
|
||||
('gmv', models.CharField(blank=True, max_length=50, verbose_name='GMV')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='邮箱')),
|
||||
('phone', models.CharField(blank=True, max_length=50, verbose_name='手机号|WhatsApp')),
|
||||
('account_type', models.CharField(blank=True, max_length=50, verbose_name='账号属性')),
|
||||
('price_quote', models.CharField(blank=True, max_length=100, verbose_name='报价')),
|
||||
('response_speed', models.CharField(blank=True, max_length=50, verbose_name='回复速度')),
|
||||
('cooperation_intention', models.CharField(blank=True, max_length=50, verbose_name='合作意向')),
|
||||
('payment_method', models.CharField(blank=True, max_length=50, verbose_name='支付方式')),
|
||||
('payment_account', models.CharField(blank=True, max_length=100, verbose_name='收款账号')),
|
||||
('address', models.TextField(blank=True, verbose_name='收件地址')),
|
||||
('has_ooin', models.CharField(blank=True, max_length=10, verbose_name='签约OOIN?')),
|
||||
('source', models.CharField(blank=True, max_length=100, verbose_name='渠道来源')),
|
||||
('contact_status', models.CharField(blank=True, max_length=50, verbose_name='建联进度')),
|
||||
('cooperation_brands', models.JSONField(blank=True, default=list, verbose_name='合作品牌')),
|
||||
('system_categories', models.CharField(blank=True, max_length=100, verbose_name='系统展示的带货品类')),
|
||||
('actual_categories', models.CharField(blank=True, max_length=100, verbose_name='实际高播放量带货品类')),
|
||||
('human_categories', models.CharField(blank=True, max_length=100, verbose_name='达人标想要货品类')),
|
||||
('creator_base', models.CharField(blank=True, max_length=100, verbose_name='达人base')),
|
||||
('notes', models.TextField(blank=True, verbose_name='父记录')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '创作者数据',
|
||||
'verbose_name_plural': '创作者数据',
|
||||
'db_table': 'feishu_creators',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
@ -73,79 +110,22 @@ class Migration(migrations.Migration):
|
||||
name='KnowledgeBase',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100, verbose_name='知识库名称')),
|
||||
('user_id', models.UUIDField(verbose_name='创建者ID')),
|
||||
('name', models.CharField(max_length=100, unique=True, verbose_name='知识库名称')),
|
||||
('desc', models.TextField(blank=True, null=True, verbose_name='知识库描述')),
|
||||
('type', models.CharField(choices=[('admin', '管理级知识库'), ('leader', '部门级知识库'), ('member', '成员级知识库'), ('private', '私有知识库'), ('secret', '公司级别私密知识库')], default='private', max_length=20, verbose_name='知识库类型')),
|
||||
('department', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('group', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('user_id', models.CharField(max_length=50, verbose_name='创建者ID')),
|
||||
('documents', models.JSONField(default=list)),
|
||||
('char_length', models.IntegerField(default=0)),
|
||||
('document_count', models.IntegerField(default=0)),
|
||||
('external_id', models.UUIDField(blank=True, null=True)),
|
||||
('create_time', models.DateTimeField(auto_now_add=True)),
|
||||
('update_time', models.DateTimeField(auto_now=True)),
|
||||
('owners', models.ManyToManyField(related_name='owned_knowledge_bases', to=settings.AUTH_USER_MODEL, verbose_name='所有者')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'knowledge_bases',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ChatHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('conversation_id', models.CharField(db_index=True, max_length=100)),
|
||||
('parent_id', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('role', models.CharField(choices=[('user', '用户'), ('assistant', 'AI助手'), ('system', '系统')], max_length=20)),
|
||||
('content', models.TextField()),
|
||||
('tokens', models.IntegerField(default=0, help_text='消息token数')),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user_management.knowledgebase')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'chat_history',
|
||||
'ordering': ['created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='KnowledgeBasePermission',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('can_read', models.BooleanField(default=False, verbose_name='查看权限')),
|
||||
('can_edit', models.BooleanField(default=False, verbose_name='修改权限')),
|
||||
('can_delete', models.BooleanField(default=False, verbose_name='删除权限')),
|
||||
('status', models.CharField(choices=[('active', '生效中'), ('expired', '已过期'), ('revoked', '已撤销')], default='active', max_length=10, verbose_name='状态')),
|
||||
('granted_at', models.DateTimeField(auto_now_add=True, verbose_name='授权时间')),
|
||||
('expires_at', models.DateTimeField(blank=True, null=True, verbose_name='过期时间')),
|
||||
('granted_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='granted_permissions', to=settings.AUTH_USER_MODEL, verbose_name='授权人')),
|
||||
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_permissions', to='user_management.knowledgebase', verbose_name='知识库')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='knowledge_base_permissions', to=settings.AUTH_USER_MODEL, verbose_name='用户')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '知识库权限',
|
||||
'verbose_name_plural': '知识库权限',
|
||||
'db_table': 'knowledge_base_permissions',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Notification',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('type', models.CharField(choices=[('permission_request', '权限申请'), ('permission_approved', '权限批准'), ('permission_rejected', '权限拒绝'), ('permission_expired', '权限过期'), ('system_notice', '系统通知')], max_length=20)),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('content', models.TextField()),
|
||||
('is_read', models.BooleanField(default=False)),
|
||||
('related_resource', models.CharField(blank=True, max_length=100)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_notifications', to=settings.AUTH_USER_MODEL)),
|
||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_notifications', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['type'], name='knowledge_b_type_0439e7_idx'), models.Index(fields=['department'], name='knowledge_b_departm_e739fd_idx'), models.Index(fields=['group'], name='knowledge_b_group_3dcf34_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -181,40 +161,65 @@ class Migration(migrations.Migration):
|
||||
'db_table': 'user_profiles',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='knowledgebase',
|
||||
index=models.Index(fields=['type'], name='knowledge_b_type_0439e7_idx'),
|
||||
migrations.CreateModel(
|
||||
name='ChatHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('conversation_id', models.CharField(db_index=True, max_length=100)),
|
||||
('parent_id', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('role', models.CharField(choices=[('user', '用户'), ('assistant', 'AI助手'), ('system', '系统')], max_length=20)),
|
||||
('content', models.TextField()),
|
||||
('tokens', models.IntegerField(default=0, help_text='消息token数')),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text="\n {\n 'model_id': 'xxx',\n 'dataset_id_list': ['id1', 'id2', ...],\n 'dataset_external_id_list': ['ext1', 'ext2', ...],\n 'primary_knowledge_base': 'id1'\n }\n ")),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user_management.knowledgebase')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'chat_history',
|
||||
'ordering': ['created_at'],
|
||||
'indexes': [models.Index(fields=['conversation_id', 'created_at'], name='chat_histor_convers_33721a_idx'), models.Index(fields=['user', 'created_at'], name='chat_histor_user_id_aa050a_idx'), models.Index(fields=['conversation_id', 'is_deleted'], name='chat_histor_convers_89bc43_idx')],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='knowledgebase',
|
||||
index=models.Index(fields=['department'], name='knowledge_b_departm_e739fd_idx'),
|
||||
migrations.CreateModel(
|
||||
name='KnowledgeBasePermission',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('can_read', models.BooleanField(default=False, verbose_name='查看权限')),
|
||||
('can_edit', models.BooleanField(default=False, verbose_name='修改权限')),
|
||||
('can_delete', models.BooleanField(default=False, verbose_name='删除权限')),
|
||||
('status', models.CharField(choices=[('active', '生效中'), ('expired', '已过期'), ('revoked', '已撤销')], default='active', max_length=10, verbose_name='状态')),
|
||||
('granted_at', models.DateTimeField(auto_now_add=True, verbose_name='授权时间')),
|
||||
('expires_at', models.DateTimeField(blank=True, null=True, verbose_name='过期时间')),
|
||||
('granted_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='granted_permissions', to=settings.AUTH_USER_MODEL, verbose_name='授权人')),
|
||||
('knowledge_base', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_permissions', to='user_management.knowledgebase', verbose_name='知识库')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='knowledge_base_permissions', to=settings.AUTH_USER_MODEL, verbose_name='用户')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '知识库权限',
|
||||
'verbose_name_plural': '知识库权限',
|
||||
'db_table': 'knowledge_base_permissions',
|
||||
'indexes': [models.Index(fields=['knowledge_base', 'user', 'status'], name='knowledge_b_knowled_88e81e_idx')],
|
||||
'unique_together': {('knowledge_base', 'user')},
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='knowledgebase',
|
||||
index=models.Index(fields=['group'], name='knowledge_b_group_3dcf34_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='chathistory',
|
||||
index=models.Index(fields=['conversation_id', 'created_at'], name='chat_histor_convers_33721a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='chathistory',
|
||||
index=models.Index(fields=['user', 'created_at'], name='chat_histor_user_id_aa050a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='knowledgebasepermission',
|
||||
index=models.Index(fields=['knowledge_base', 'user', 'status'], name='knowledge_b_knowled_88e81e_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='knowledgebasepermission',
|
||||
unique_together={('knowledge_base', 'user')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notification',
|
||||
index=models.Index(fields=['receiver', '-created_at'], name='user_manage_receive_fcb0eb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notification',
|
||||
index=models.Index(fields=['type', 'is_read'], name='user_manage_type_362052_idx'),
|
||||
migrations.CreateModel(
|
||||
name='Notification',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('type', models.CharField(choices=[('permission_request', '权限申请'), ('permission_approved', '权限批准'), ('permission_rejected', '权限拒绝'), ('permission_expired', '权限过期'), ('system_notice', '系统通知')], max_length=20)),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('content', models.TextField()),
|
||||
('is_read', models.BooleanField(default=False)),
|
||||
('related_resource', models.CharField(blank=True, max_length=100)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_notifications', to=settings.AUTH_USER_MODEL)),
|
||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_notifications', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['receiver', '-created_at'], name='user_manage_receive_fcb0eb_idx'), models.Index(fields=['type', 'is_read'], name='user_manage_type_362052_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-28 07:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user_management', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='feishucreator',
|
||||
name='gmv',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='GMV'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='feishucreator',
|
||||
name='price_quote',
|
||||
field=models.TextField(blank=True, verbose_name='报价'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='feishucreator',
|
||||
name='tiktok_url',
|
||||
field=models.TextField(blank=True, verbose_name='链接'),
|
||||
),
|
||||
]
|
@ -1,27 +0,0 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-26 02:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user_management', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='knowledgebase',
|
||||
name='owners',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='knowledgebase',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, unique=True, verbose_name='知识库名称'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='knowledgebase',
|
||||
name='user_id',
|
||||
field=models.UUIDField(verbose_name='创建者ID'),
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-28 07:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user_management', '0002_alter_feishucreator_gmv_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='feishucreator',
|
||||
name='handle',
|
||||
field=models.TextField(blank=True, verbose_name='Handle'),
|
||||
),
|
||||
]
|
@ -1,3 +1,4 @@
|
||||
from itertools import count
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
@ -286,13 +287,23 @@ class ChatHistory(models.Model):
|
||||
]
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
# 保留与主知识库的关联
|
||||
knowledge_base = models.ForeignKey('KnowledgeBase', on_delete=models.CASCADE)
|
||||
# 用于标识知识库组合的对话
|
||||
conversation_id = models.CharField(max_length=100, db_index=True)
|
||||
parent_id = models.CharField(max_length=100, null=True, blank=True)
|
||||
role = models.CharField(max_length=20, choices=ROLE_CHOICES)
|
||||
content = models.TextField()
|
||||
tokens = models.IntegerField(default=0, help_text="消息token数")
|
||||
metadata = models.JSONField(default=dict, blank=True)
|
||||
# 扩展metadata字段,用于存储知识库组合信息
|
||||
metadata = models.JSONField(default=dict, blank=True, help_text="""
|
||||
{
|
||||
'model_id': 'xxx',
|
||||
'dataset_id_list': ['id1', 'id2', ...],
|
||||
'dataset_external_id_list': ['ext1', 'ext2', ...],
|
||||
'primary_knowledge_base': 'id1'
|
||||
}
|
||||
""")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
is_deleted = models.BooleanField(default=False)
|
||||
|
||||
@ -302,6 +313,8 @@ class ChatHistory(models.Model):
|
||||
indexes = [
|
||||
models.Index(fields=['conversation_id', 'created_at']),
|
||||
models.Index(fields=['user', 'created_at']),
|
||||
# 添加新的索引以支持知识库组合查询
|
||||
models.Index(fields=['conversation_id', 'is_deleted']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
@ -315,11 +328,69 @@ class ChatHistory(models.Model):
|
||||
is_deleted=False
|
||||
).order_by('created_at')
|
||||
|
||||
@classmethod
|
||||
def get_conversations_by_knowledge_bases(cls, dataset_ids, user):
|
||||
"""根据知识库组合获取对话历史"""
|
||||
# 对知识库ID列表排序以确保一致性
|
||||
sorted_kb_ids = sorted(dataset_ids)
|
||||
conversation_id = str(uuid.uuid5(
|
||||
uuid.NAMESPACE_DNS,
|
||||
'-'.join(sorted_kb_ids)
|
||||
))
|
||||
|
||||
return cls.objects.filter(
|
||||
conversation_id=conversation_id,
|
||||
user=user,
|
||||
is_deleted=False
|
||||
).order_by('created_at')
|
||||
|
||||
@classmethod
|
||||
def get_knowledge_base_combinations(cls, user):
|
||||
"""获取用户的所有知识库组合"""
|
||||
return cls.objects.filter(
|
||||
user=user,
|
||||
is_deleted=False
|
||||
).values('conversation_id').annotate(
|
||||
last_message=max('created_at'),
|
||||
message_count=count('id')
|
||||
).values(
|
||||
'conversation_id',
|
||||
'last_message',
|
||||
'message_count',
|
||||
'metadata'
|
||||
).order_by('-last_message')
|
||||
|
||||
def get_knowledge_bases(self):
|
||||
"""获取此消息关联的所有知识库"""
|
||||
if self.metadata and 'dataset_id_list' in self.metadata:
|
||||
return KnowledgeBase.objects.filter(
|
||||
id__in=self.metadata['dataset_id_list']
|
||||
)
|
||||
return KnowledgeBase.objects.filter(id=self.knowledge_base.id)
|
||||
|
||||
def soft_delete(self):
|
||||
"""软删除消息"""
|
||||
self.is_deleted = True
|
||||
self.save()
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典格式"""
|
||||
return {
|
||||
'id': str(self.id),
|
||||
'conversation_id': self.conversation_id,
|
||||
'role': self.role,
|
||||
'content': self.content,
|
||||
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'metadata': self.metadata,
|
||||
'knowledge_bases': [
|
||||
{
|
||||
'id': str(kb.id),
|
||||
'name': kb.name,
|
||||
'type': kb.type
|
||||
} for kb in self.get_knowledge_bases()
|
||||
]
|
||||
}
|
||||
|
||||
class UserProfile(models.Model):
|
||||
"""用户档案模型"""
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
||||
@ -547,3 +618,54 @@ class KnowledgeBase(models.Model):
|
||||
embedding_mode_id=embedding_mode_id,
|
||||
document_count=len(data["documents"]) if data.get("documents") else 0
|
||||
)
|
||||
|
||||
class FeishuCreator(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
record_id = models.CharField(max_length=100, unique=True, verbose_name='飞书记录ID')
|
||||
|
||||
# 对接人信息
|
||||
contact_person = models.CharField(max_length=50, blank=True, verbose_name='对接人')
|
||||
|
||||
# 基本账号信息
|
||||
handle = models.TextField(blank=True, verbose_name='Handle')
|
||||
tiktok_url = models.TextField(blank=True, verbose_name='链接')
|
||||
fans_count = models.CharField(max_length=50, blank=True, verbose_name='粉丝数')
|
||||
gmv = models.CharField(max_length=100, blank=True, verbose_name='GMV')
|
||||
|
||||
# 联系方式
|
||||
email = models.EmailField(blank=True, verbose_name='邮箱')
|
||||
phone = models.CharField(max_length=50, blank=True, verbose_name='手机号|WhatsApp')
|
||||
|
||||
# 账号属性和报价
|
||||
account_type = models.CharField(max_length=50, blank=True, verbose_name='账号属性')
|
||||
price_quote = models.TextField(blank=True, verbose_name='报价')
|
||||
response_speed = models.CharField(max_length=50, blank=True, verbose_name='回复速度')
|
||||
|
||||
# 合作相关
|
||||
cooperation_intention = models.CharField(max_length=50, blank=True, verbose_name='合作意向')
|
||||
payment_method = models.CharField(max_length=50, blank=True, verbose_name='支付方式')
|
||||
payment_account = models.CharField(max_length=100, blank=True, verbose_name='收款账号')
|
||||
address = models.TextField(blank=True, verbose_name='收件地址')
|
||||
has_ooin = models.CharField(max_length=10, blank=True, verbose_name='签约OOIN?')
|
||||
|
||||
# 渠道和进度
|
||||
source = models.CharField(max_length=100, blank=True, verbose_name='渠道来源')
|
||||
contact_status = models.CharField(max_length=50, blank=True, verbose_name='建联进度')
|
||||
cooperation_brands = models.JSONField(default=list, blank=True, verbose_name='合作品牌')
|
||||
|
||||
# 品类信息
|
||||
system_categories = models.CharField(max_length=100, blank=True, verbose_name='系统展示的带货品类')
|
||||
actual_categories = models.CharField(max_length=100, blank=True, verbose_name='实际高播放量带货品类')
|
||||
human_categories = models.CharField(max_length=100, blank=True, verbose_name='达人标想要货品类')
|
||||
|
||||
# 其他信息
|
||||
creator_base = models.CharField(max_length=100, blank=True, verbose_name='达人base')
|
||||
notes = models.TextField(blank=True, verbose_name='父记录')
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'feishu_creators'
|
||||
verbose_name = '创作者数据'
|
||||
verbose_name_plural = '创作者数据'
|
||||
|
@ -1091,46 +1091,51 @@ class KnowledgeBaseViewSet(viewsets.ModelViewSet):
|
||||
def get_queryset(self):
|
||||
"""获取用户有权限查看的知识库列表"""
|
||||
user = self.request.user
|
||||
all_knowledge_bases = KnowledgeBase.objects.all()
|
||||
queryset = KnowledgeBase.objects.all()
|
||||
|
||||
# 首先过滤出用户创建的或有显式读权限的知识库
|
||||
explicit_permission_kbs = KnowledgeBase.objects.filter(
|
||||
Q(user_id=user.id) | # 是创建者
|
||||
Q( # 或在权限表中有读权限
|
||||
# 1. 构建基础权限条件
|
||||
permission_conditions = Q()
|
||||
|
||||
# 2. 所有用户都可以看到 admin 类型的知识库
|
||||
permission_conditions |= Q(type='admin')
|
||||
|
||||
# 3. 用户可以看到自己创建的所有知识库
|
||||
permission_conditions |= Q(user_id=user.id)
|
||||
|
||||
# 4. 添加显式权限条件
|
||||
permission_conditions |= Q(
|
||||
id__in=KBPermissionModel.objects.filter(
|
||||
user=user,
|
||||
can_read=True,
|
||||
status='active'
|
||||
status='active',
|
||||
expires_at__gt=timezone.now()
|
||||
).values_list('knowledge_base_id', flat=True)
|
||||
)
|
||||
)
|
||||
|
||||
# 获取显式权限知识库的ID集合
|
||||
explicit_kb_ids = set(explicit_permission_kbs.values_list('id', flat=True))
|
||||
|
||||
# 对于没有显式权限的知识库,使用_can_read方法判断
|
||||
result_kbs = list(explicit_permission_kbs)
|
||||
|
||||
# 如果用户是管理员,可以查看所有知识库
|
||||
# 5. 根据用户角色添加隐式权限
|
||||
if user.role == 'admin':
|
||||
return all_knowledge_bases
|
||||
|
||||
# 否则,根据角色和部门/组筛选
|
||||
for kb in all_knowledge_bases:
|
||||
if kb.id not in explicit_kb_ids:
|
||||
has_read_permission = self._can_read(
|
||||
type=kb.type,
|
||||
user=user,
|
||||
department=kb.department,
|
||||
group=kb.group,
|
||||
creator_id=kb.user_id
|
||||
# 管理员可以看到除了其他用户 private 类型外的所有知识库
|
||||
permission_conditions |= ~Q(type='private') | Q(user_id=user.id)
|
||||
elif user.role == 'leader':
|
||||
# 组长可以查看本部门的 leader 和 member 类型知识库
|
||||
permission_conditions |= Q(
|
||||
type__in=['leader', 'member'],
|
||||
department=user.department
|
||||
)
|
||||
elif user.role in ['member', 'user']:
|
||||
# 成员可以查看本部门的 leader 类型知识库
|
||||
permission_conditions |= Q(
|
||||
type='leader',
|
||||
department=user.department
|
||||
)
|
||||
# 成员可以查看本部门本组的 member 类型知识库
|
||||
permission_conditions |= Q(
|
||||
type='member',
|
||||
department=user.department,
|
||||
group=user.group
|
||||
)
|
||||
if has_read_permission:
|
||||
result_kbs.append(kb)
|
||||
|
||||
# 转换为queryset并去重
|
||||
kb_ids = [kb.id for kb in result_kbs]
|
||||
return KnowledgeBase.objects.filter(id__in=kb_ids).distinct()
|
||||
return queryset.filter(permission_conditions).distinct()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
try:
|
||||
@ -1338,8 +1343,15 @@ class KnowledgeBaseViewSet(viewsets.ModelViewSet):
|
||||
|
||||
def _can_edit(self, type, user, department=None, group=None, creator_id=None):
|
||||
"""判断用户是否有编辑权限"""
|
||||
# admin 类型知识库所有用户都有编辑权限
|
||||
if type == 'admin':
|
||||
return True
|
||||
|
||||
if user.role == 'admin':
|
||||
return True # 管理员对所有知识库都有编辑权限
|
||||
# 管理员对其他用户的 private 类型没有编辑权限
|
||||
if type == 'private' and str(user.id) != str(creator_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
if type == 'secret':
|
||||
return False # 除管理员外,其他人无权编辑 secret 类型知识库
|
||||
@ -1363,10 +1375,17 @@ class KnowledgeBaseViewSet(viewsets.ModelViewSet):
|
||||
|
||||
def _can_delete(self, type, user, department=None, group=None, creator_id=None):
|
||||
"""判断用户是否有删除权限"""
|
||||
if user.role == 'admin':
|
||||
return True # 管理员对所有知识库都有删除权限
|
||||
# admin 类型知识库所有用户都有删除权限
|
||||
if type == 'admin':
|
||||
return True
|
||||
|
||||
if type in ['admin', 'secret']:
|
||||
if user.role == 'admin':
|
||||
# 管理员对其他用户的 private 类型没有删除权限
|
||||
if type == 'private' and str(user.id) != str(creator_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
if type == 'secret':
|
||||
return False # 除管理员外,其他人无权删除 admin 和 secret 类型知识库
|
||||
|
||||
if type == 'leader':
|
||||
@ -1385,8 +1404,15 @@ class KnowledgeBaseViewSet(viewsets.ModelViewSet):
|
||||
|
||||
def _can_read(self, type, user, department=None, group=None, creator_id=None):
|
||||
"""判断用户是否有读取权限"""
|
||||
# admin 类型知识库所有用户都有读取权限
|
||||
if type == 'admin':
|
||||
return True
|
||||
|
||||
if user.role == 'admin':
|
||||
return True # 管理员可以读取所有知识库
|
||||
# 管理员对其他用户的 private 类型没有读取权限
|
||||
if type == 'private' and str(user.id) != str(creator_id):
|
||||
return False
|
||||
return True
|
||||
|
||||
if type == 'secret':
|
||||
return False # 除管理员外,secret 类型知识库对其他人不可读
|
||||
|
Loading…
Reference in New Issue
Block a user