init
This commit is contained in:
6
.idea/MarsCodeWorkspaceAppSettings.xml
generated
Normal file
6
.idea/MarsCodeWorkspaceAppSettings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
|
||||
<option name="progress" value="1.0" />
|
||||
</component>
|
||||
</project>
|
82
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
82
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,82 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="8">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
||||
<item index="4" class="java.lang.String" itemvalue="embed" />
|
||||
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||
<item index="6" class="java.lang.String" itemvalue="following" />
|
||||
<item index="7" class="java.lang.String" itemvalue="schema" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredUrls">
|
||||
<list>
|
||||
<option value="http://localhost" />
|
||||
<option value="http://127.0.0.1" />
|
||||
<option value="http://0.0.0.0" />
|
||||
<option value="http://www.w3.org/" />
|
||||
<option value="http://json-schema.org/draft" />
|
||||
<option value="http://java.sun.com/" />
|
||||
<option value="http://xmlns.jcp.org/" />
|
||||
<option value="http://javafx.com/javafx/" />
|
||||
<option value="http://javafx.com/fxml" />
|
||||
<option value="http://maven.apache.org/xsd/" />
|
||||
<option value="http://maven.apache.org/POM/" />
|
||||
<option value="http://www.springframework.org/schema/" />
|
||||
<option value="http://www.springframework.org/tags" />
|
||||
<option value="http://www.springframework.org/security/tags" />
|
||||
<option value="http://www.thymeleaf.org" />
|
||||
<option value="http://www.jboss.org/j2ee/schema/" />
|
||||
<option value="http://www.jboss.com/xml/ns/" />
|
||||
<option value="http://www.ibm.com/webservices/xsd" />
|
||||
<option value="http://activemq.apache.org/schema/" />
|
||||
<option value="http://schema.cloudfoundry.org/spring/" />
|
||||
<option value="http://schemas.xmlsoap.org/" />
|
||||
<option value="http://cxf.apache.org/schemas/" />
|
||||
<option value="http://primefaces.org/ui" />
|
||||
<option value="http://tiles.apache.org/" />
|
||||
<option value="http://one.zhaoxueqing.top" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="20">
|
||||
<item index="0" class="java.lang.String" itemvalue="sentence_transformers" />
|
||||
<item index="1" class="java.lang.String" itemvalue="tencentcloud-sdk-python-ocr" />
|
||||
<item index="2" class="java.lang.String" itemvalue="tencentcloud-sdk-python" />
|
||||
<item index="3" class="java.lang.String" itemvalue="Pillow" />
|
||||
<item index="4" class="java.lang.String" itemvalue="PyJWT" />
|
||||
<item index="5" class="java.lang.String" itemvalue="pydantic" />
|
||||
<item index="6" class="java.lang.String" itemvalue="requests" />
|
||||
<item index="7" class="java.lang.String" itemvalue="websocket-client" />
|
||||
<item index="8" class="java.lang.String" itemvalue="pandas" />
|
||||
<item index="9" class="java.lang.String" itemvalue="openai" />
|
||||
<item index="10" class="java.lang.String" itemvalue="fastapi" />
|
||||
<item index="11" class="java.lang.String" itemvalue="docx2txt" />
|
||||
<item index="12" class="java.lang.String" itemvalue="langchain" />
|
||||
<item index="13" class="java.lang.String" itemvalue="starlette" />
|
||||
<item index="14" class="java.lang.String" itemvalue="grpcio" />
|
||||
<item index="15" class="java.lang.String" itemvalue="uvicorn" />
|
||||
<item index="16" class="java.lang.String" itemvalue="pymilvus" />
|
||||
<item index="17" class="java.lang.String" itemvalue="snowland-smx" />
|
||||
<item index="18" class="java.lang.String" itemvalue="openpyxl" />
|
||||
<item index="19" class="java.lang.String" itemvalue="eio" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11 (image_recognition)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (scf)" project-jdk-type="Python SDK" />
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/scf-python-dev-demo.iml" filepath="$PROJECT_DIR$/.idea/scf-python-dev-demo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
12
.idea/scf-python-dev-demo.iml
generated
Normal file
12
.idea/scf-python-dev-demo.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.9 (scf)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 快速构建 scf-python
|
||||
|
||||
**中文** | [English](./README_EN.md)
|
||||
|
||||
## 简介
|
||||
|
||||
scf-python 模板使用 Tencent SCF 组件及其触发器能力,方便的在腾讯云创建,配置和管理一个 scf-python 应用。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装
|
||||
|
||||
```bash
|
||||
# 安装 Serverless Cloud Framework
|
||||
npm install -g serverless-cloud-framework
|
||||
```
|
||||
|
||||
### 2. 创建
|
||||
|
||||
通过如下命令直接下载该例子:
|
||||
|
||||
```bash
|
||||
scf init scf-python --name example
|
||||
cd example
|
||||
```
|
||||
|
||||
### 3. 部署
|
||||
|
||||
在 `serverless.yml` 文件所在的项目根目录,运行以下指令,将会弹出二维码,直接扫码授权进行部署:
|
||||
|
||||
```bash
|
||||
scf deploy
|
||||
```
|
||||
|
||||
> **说明**:如果鉴权失败,请参考 [权限配置](https://cloud.tencent.com/document/product/1154/43006) 进行授权。
|
||||
|
||||
### 4. 查看状态
|
||||
|
||||
执行以下命令,查看您部署的项目信息:
|
||||
|
||||
```bash
|
||||
scf info
|
||||
```
|
||||
|
||||
### 5. 移除
|
||||
|
||||
可以通过以下命令移除 scf-python 应用
|
||||
|
||||
```bash
|
||||
scf remove
|
||||
```
|
||||
|
||||
### 账号配置(可选)
|
||||
|
||||
serverless 默认支持扫描二维码登录,用户扫描二维码后会自动生成一个 `.env` 文件并将密钥存入其中.
|
||||
如您希望配置持久的环境变量/秘钥信息,也可以本地创建 `.env` 文件,
|
||||
把从[API 密钥管理](https://console.cloud.tencent.com/cam/capi)中获取的 `SecretId` 和`SecretKey` 填入其中.
|
||||
|
||||
> 如果没有腾讯云账号,可以在此[注册新账号](https://cloud.tencent.com/register)。
|
||||
|
||||
```bash
|
||||
# 腾讯云的配置信息
|
||||
touch .env
|
||||
```
|
||||
|
||||
```
|
||||
# .env file
|
||||
TENCENT_SECRET_ID=123
|
||||
TENCENT_SECRET_KEY=123
|
||||
```
|
80
README_EN.md
Normal file
80
README_EN.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Quickly create and deploy scf-python application
|
||||
|
||||
[中文](./README.md) | **English**
|
||||
|
||||
## Introduction
|
||||
|
||||
Easily deploy scf-python applications to Tencent Cloud's serverless infrastructure using this Serverless Cloud Framework Component.
|
||||
Your application will auto-scale, never charge you for idle time, and require little-to-zero administration.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install
|
||||
|
||||
```bash
|
||||
# Install Serverless Cloud Framework
|
||||
npm install -g serverless-cloud-framework
|
||||
```
|
||||
|
||||
### 2. Initialize
|
||||
|
||||
Initializing the scf-python template by running this following command:
|
||||
|
||||
```bash
|
||||
scf init scf-python --name example
|
||||
cd example
|
||||
```
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
You can use following command to deploy the APP.
|
||||
|
||||
```bash
|
||||
cd scf-python
|
||||
scf deploy
|
||||
```
|
||||
|
||||
This command will walk you through signing up a Tencent Cloud Account to deploy the APP.
|
||||
|
||||
### 4. Monitor
|
||||
|
||||
Anytime you need to know more about your running express instance, you can run `scf info` to view the most critical info.
|
||||
This is especially helpful when you want to know the outputs of your instances so that you can reference them in another instance.
|
||||
You will also see a url where you'll be able to view more info about your instance on the Serverless Dashboard.
|
||||
|
||||
It also shows you the status of your instance, when it was last deployed, and how many times it was deployed.
|
||||
To dig even deeper, you can pass the --debug flag to view the state of your component instance in case the deployment failed for any reason.
|
||||
|
||||
```bash
|
||||
scf info
|
||||
```
|
||||
|
||||
### 5. Remove
|
||||
|
||||
If you wanna tear down your entire infrastructure that was created during deployment,
|
||||
just run `scf remove` and serverless will remove all the data it needs from the built-in state storage system to delete only the relevant cloud resources that it created.
|
||||
|
||||
```bash
|
||||
scf remove
|
||||
```
|
||||
|
||||
### Setting up credentials (Optional)
|
||||
|
||||
By default, you are able to login your Tencent Cloud account by scanning QR code and an `.env` file with credentials is auto generated.
|
||||
The credentials will be expired after 2 hours.
|
||||
If you would like to use persistent credentials,
|
||||
you can [create an API Key here](https://console.cloud.tencent.com/cam/capi) and add the `SecretId` and `SecretKey` into the `.env` file
|
||||
|
||||
> If you don's have a Tencent Cloud Account, you can register [here](https://cloud.tencent.com/register)
|
||||
|
||||
```bash
|
||||
# Add your Tencent credentials here
|
||||
touch .env
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
# .env file
|
||||
TENCENT_SECRET_ID=123
|
||||
TENCENT_SECRET_KEY=123
|
||||
```
|
220
access_token.py
Normal file
220
access_token.py
Normal file
@@ -0,0 +1,220 @@
|
||||
import requests
|
||||
import time
|
||||
|
||||
|
||||
def transform_data(data, mapping):
|
||||
temp = {}
|
||||
for k, v in mapping.items():
|
||||
if k in data:
|
||||
if k == "date":
|
||||
timestamp = str(int(time.mktime(time.strptime(data[k], "%Y-%m-%d %H:%M:%S"))))
|
||||
temp[v] = timestamp + "000"
|
||||
else:
|
||||
temp[v] = data[k]
|
||||
return temp
|
||||
|
||||
|
||||
class SmartSheet:
|
||||
def __init__(self, doc_id, sheet_id):
|
||||
self.doc_id = doc_id
|
||||
self.sheet_id = sheet_id
|
||||
self._columns = None
|
||||
self._data = None
|
||||
|
||||
@staticmethod
|
||||
def _post(url, body):
|
||||
resp = requests.post(url, json=body)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
return {}
|
||||
|
||||
def _get_columns(self):
|
||||
url = "https://a.cmdp.cn/umss/v1/wxw/common/submit"
|
||||
body = {
|
||||
"url": "https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/get_fields",
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"docid": self.doc_id,
|
||||
"sheet_id": self.sheet_id
|
||||
}
|
||||
}
|
||||
response = self._post(url, body)
|
||||
if response:
|
||||
fields = response["fields"]
|
||||
return fields
|
||||
return []
|
||||
|
||||
def _get_data(self, filters: dict, cursor: int = 0, limit: int = 1000):
|
||||
url = "https://a.cmdp.cn/umss/v1/wxw/smartSheet/getRecords"
|
||||
body = {
|
||||
"docid": self.doc_id,
|
||||
"sheet_id": self.sheet_id,
|
||||
"filter_spec": filters,
|
||||
"offset": cursor,
|
||||
"limit": limit
|
||||
}
|
||||
response = self._post(url, body)
|
||||
if response:
|
||||
cursor = response["next"]
|
||||
records = response["records"]
|
||||
return cursor, records
|
||||
return 0, []
|
||||
|
||||
def _add(self, records: list):
|
||||
url = "https://a.cmdp.cn/umss/v1/wxw/smartSheet/addRecords"
|
||||
body = {
|
||||
"docid": self.doc_id,
|
||||
"sheet_id": self.sheet_id,
|
||||
"key_type": "CELL_VALUE_KEY_TYPE_FIELD_TITLE",
|
||||
"records": records
|
||||
}
|
||||
response = self._post(url, body)
|
||||
if response:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _transform_data(self, data: dict):
|
||||
result = {}
|
||||
columns = self.columns
|
||||
for i in data:
|
||||
type_info = columns[i]
|
||||
i_type = type_info.get("field_type")
|
||||
if i_type == "FIELD_TYPE_TEXT":
|
||||
result[i] = [
|
||||
{
|
||||
"text": data[i],
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
elif i_type == "FIELD_TYPE_SINGLE_SELECT":
|
||||
options = {i["text"]: i for i in type_info['property_single_select']["options"]}
|
||||
result[i] = [
|
||||
options[data[i]]
|
||||
]
|
||||
else:
|
||||
result[i] = data[i]
|
||||
return result
|
||||
|
||||
def _convert_data(self, data: dict):
|
||||
va = {}
|
||||
for v in data:
|
||||
type_info = self.columns[v]
|
||||
i_type = type_info.get("field_type")
|
||||
if i_type == "FIELD_TYPE_TEXT":
|
||||
va[v] = "".join([m["text"] for m in data[v]])
|
||||
elif i_type == "FIELD_TYPE_DATE_TIME":
|
||||
va[v] = 0 if data[v] is None else data[v]
|
||||
elif i_type == "FIELD_TYPE_NUMBER":
|
||||
va[v] = 0 if data[v] is None else data[v]
|
||||
elif i_type == "FIELD_TYPE_SELECT":
|
||||
va[v] = [m["text"] for m in data[v]]
|
||||
elif i_type == "FIELD_TYPE_SINGLE_SELECT":
|
||||
va[v] = data[v][0]["text"] if data[v] else ""
|
||||
elif i_type == "FIELD_TYPE_TWOWAYLINKRECORDS":
|
||||
va[v] = data[v]
|
||||
else:
|
||||
print(i_type, data[v],type_info)
|
||||
return va
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
if self._columns is None:
|
||||
self._columns = self._get_columns()
|
||||
return {i["field_title"]: i for i in self._columns}
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
if self._data is None:
|
||||
self._data = self.search({})
|
||||
return self._data
|
||||
|
||||
def search(self, filters: dict):
|
||||
cursor = 0
|
||||
result = []
|
||||
while True:
|
||||
cursor, records = self._get_data(filters, cursor=cursor)
|
||||
result += records
|
||||
if cursor == 0:
|
||||
break
|
||||
return result
|
||||
|
||||
def add(self, data: dict):
|
||||
return self._add([{"values": self._transform_data(data)}])
|
||||
|
||||
def batch_add(self, data_list: list):
|
||||
return self._add([{"values": self._transform_data(i)} for i in data_list])
|
||||
|
||||
def to_json(self):
|
||||
data = []
|
||||
for i in self.data:
|
||||
values = i["values"]
|
||||
va = self._convert_data(values)
|
||||
va['_rid'] = i["record_id"]
|
||||
data.append(va)
|
||||
return {"columns": self.columns, "data": data}
|
||||
|
||||
|
||||
class SmartTableApi:
|
||||
def __init__(self, doc_id):
|
||||
self.doc_id = doc_id
|
||||
self.metadata_id = None
|
||||
self._metadata = None
|
||||
self.metadata_table = None
|
||||
self._get_sheet_list()
|
||||
|
||||
@staticmethod
|
||||
def _post(url, body):
|
||||
resp = requests.post(url, json=body)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
return {}
|
||||
|
||||
def _get_sheet_list(self):
|
||||
url = "https://a.cmdp.cn/umss/v1/wxw/common/submit"
|
||||
body = {
|
||||
"url": "https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/get_sheet",
|
||||
"method": "POST",
|
||||
"body": {
|
||||
"docid": self.doc_id
|
||||
|
||||
}
|
||||
}
|
||||
resp = self._post(url, body)
|
||||
sheet_list = resp.get("sheet_list", [])
|
||||
result = {i["title"]: i["sheet_id"] for i in sheet_list}
|
||||
self.metadata_id = result.get("Metadata")
|
||||
if "Metadata" in result:
|
||||
del result["Metadata"]
|
||||
self._metadata = result
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
return self._metadata
|
||||
|
||||
def _update_metadata(self, sheet_id, sheet_name):
|
||||
if not self.metadata_table:
|
||||
self.metadata_table = SmartSheet(self.doc_id, self.metadata_id)
|
||||
self.metadata_table.add({"名称": sheet_name, "类型": "sheet", "值": sheet_id})
|
||||
pass
|
||||
|
||||
def add_sheet(self, sheet_name):
|
||||
url = "https://a.cmdp.cn/umss/v1/wxw/addSheet"
|
||||
body = {
|
||||
"docid": self.doc_id,
|
||||
"properties": {
|
||||
"title": sheet_name
|
||||
}
|
||||
}
|
||||
resp = self._post(url, body)
|
||||
if resp.get("properties"):
|
||||
self._update_metadata(sheet_id=resp["properties"]["sheet_id"], sheet_name=sheet_name)
|
||||
return resp
|
||||
|
||||
def get_sheet(self, sheet_id):
|
||||
return SmartSheet(self.doc_id, sheet_id)
|
||||
|
||||
def get_sheet_by_name(self, sheet_name):
|
||||
if sheet_name in self.metadata:
|
||||
return self.get_sheet(self.metadata[sheet_name])
|
||||
else:
|
||||
return None
|
116
esmart.py
Normal file
116
esmart.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import json
|
||||
import time
|
||||
|
||||
from access_token import SmartSheet
|
||||
from lib import WXworkBaseAPI
|
||||
|
||||
|
||||
class ESmart:
|
||||
def __init__(self, time_out: int = 10 * 60):
|
||||
self.time_out = time_out
|
||||
self.timestamp = int(time.time())
|
||||
self.report_sheet = SmartSheet(
|
||||
"dc48YTUb9gNYCpIWg5v90-U5HIWT72bm3Fkw9Jvi33_sP3I0H32QEDFNOf69TS1bbUjdsYLmvArqqUootD34nm8Q", "L1wFol")
|
||||
self.user_sheet = SmartSheet(
|
||||
"dc1zao6J8YnS9p86FKKvKQadukmFXlXv3dU1qVQrgcjB81zdSVI7qoi9a7K_OpN4BTyXc8EbYVXxpnQNUUTMWwIw",
|
||||
"8vVxEx")
|
||||
self.notice_sheet = SmartSheet(
|
||||
"dc1zao6J8YnS9p86FKKvKQadukmFXlXv3dU1qVQrgcjB81zdSVI7qoi9a7K_OpN4BTyXc8EbYVXxpnQNUUTMWwIw", "2943X5")
|
||||
self.user_log_sheet = SmartSheet(
|
||||
"dcqzSDHPl2ZWoLkEB3Id1wMMaW0meMSwGLjfnuHD0VMh0DVidKdal-3wYfsh7Zb1pGn9moDuzjvhtlRZAXoO-Hjg", "g6dTI2")
|
||||
self.base_sheet = SmartSheet(
|
||||
"dcqzSDHPl2ZWoLkEB3Id1wMMaW0meMSwGLjfnuHD0VMh0DVidKdal-3wYfsh7Zb1pGn9moDuzjvhtlRZAXoO-Hjg", "1gTulR")
|
||||
self._base_data = None
|
||||
self._user_info = None
|
||||
|
||||
@staticmethod
|
||||
def _convert_user_info(data):
|
||||
_data = {}
|
||||
for i in data["data"]:
|
||||
if "来源对象ID#eid" in i:
|
||||
v = {
|
||||
"eid": i["来源对象ID#eid"],
|
||||
"name": i["名称#name"],
|
||||
"value": i["数值#value"],
|
||||
"type": i["类型"],
|
||||
"_rid": i["_rid"],
|
||||
}
|
||||
k = v["name"].split("_")
|
||||
k = k[0] if len(k) > 0 else ""
|
||||
_data[k] = v
|
||||
return _data
|
||||
|
||||
@property
|
||||
def base_data(self):
|
||||
if self._base_data is None:
|
||||
self._base_data = self.base_sheet.to_json()
|
||||
if self._check_timeout():
|
||||
self._base_data = self.base_sheet.to_json()
|
||||
return self._base_data
|
||||
|
||||
@property
|
||||
def user_info(self):
|
||||
if self._user_info is None:
|
||||
self._user_info = self._convert_user_info(self.user_sheet.to_json())
|
||||
if self._check_timeout():
|
||||
self._user_info = self._convert_user_info(self.user_sheet.to_json())
|
||||
return self._user_info
|
||||
|
||||
def get_user_info(self, user_id):
|
||||
now = int(time.time())
|
||||
if now - self.timestamp > self.time_out:
|
||||
print("refresh")
|
||||
self.timestamp = now
|
||||
|
||||
def _check_timeout(self):
|
||||
now = int(time.time())
|
||||
if now - self.timestamp > self.time_out:
|
||||
print("refresh")
|
||||
self.timestamp = now
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def refresh(self):
|
||||
self._base_data = self.base_sheet.to_json()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
esmart = ESmart()
|
||||
# with open("package.json", "w",encoding="utf-8") as f:
|
||||
# f.write(json.dumps(esmart.user_info,ensure_ascii=False))
|
||||
with open("package.json", "r",encoding="utf-8") as f:
|
||||
u_mapping = json.loads(f.read())
|
||||
cache_mapping = {v["深交所上市公司代码"]: [u_mapping.get(j,{}).get('_rid') for j in v.get("跟进负责人",[]) if u_mapping.get(j)] for v in esmart.base_data["data"]}
|
||||
print(cache_mapping)
|
||||
# user_sheet.search({
|
||||
# "conjunction": "CONJUNCTION_AND",
|
||||
# "conditions": [{
|
||||
# "field_id": 'fmTMGx',
|
||||
# "field_type": "FIELD_TYPE_TEXT",
|
||||
# "operator": "OPERATOR_CONTAINS",
|
||||
# "string_value": {
|
||||
# "value": [
|
||||
# ""
|
||||
# ]
|
||||
# }
|
||||
# }]
|
||||
# })
|
||||
# for i in m["data"]:
|
||||
# print(i)
|
||||
#
|
||||
# data = {
|
||||
# "*发布日期": time.strftime("%Y-%m-%d", time.localtime()),
|
||||
# "*栏目#gName": "",
|
||||
# "状态": "待发布",
|
||||
# "*摘要#subject": "",
|
||||
# "置顶#isTop": False,
|
||||
# "*详情#content": "",
|
||||
# "*标题#title": "",
|
||||
# "消息类型#msgType": "中性",
|
||||
# "*消息接收对象#sendList": [
|
||||
# ""
|
||||
# ]
|
||||
# }
|
||||
# print(data)
|
||||
#
|
47
groupchat/main.py
Normal file
47
groupchat/main.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# 企业微信客户群聊分析
|
||||
from os import stat_result
|
||||
|
||||
import pandas as pd
|
||||
import re
|
||||
|
||||
|
||||
def parser_chat_text(content: str):
|
||||
result = []
|
||||
temp = ["", "", "", ""]
|
||||
message = ""
|
||||
pattern = "(.*?@{0,1}.*?@{0,1}.*?) (.{1,2}/.{1,2}) (.{2}:.{2}:.{2})"
|
||||
for i in content.split("\n"):
|
||||
a = re.findall(pattern, i)
|
||||
if a:
|
||||
if message:
|
||||
temp[3] = message
|
||||
message = ""
|
||||
result.append(temp.copy())
|
||||
user, date, time = a[0]
|
||||
temp[0] = user
|
||||
temp[1] = date
|
||||
temp[2] = time
|
||||
else:
|
||||
message += i
|
||||
stat_result = {}
|
||||
for user, date, time, content in result:
|
||||
if user in stat_result:
|
||||
times, word_count = stat_result[user]
|
||||
stat_result[user] = (times + 1, word_count + len(content))
|
||||
else:
|
||||
stat_result[user] = (1, len(content))
|
||||
return result, [[t, stat_result[t][0], stat_result[t][1]] for t in stat_result]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
df = pd.read_excel("聊天记录.xlsx")
|
||||
for group_name,group_messages in df.values:
|
||||
writer = pd.ExcelWriter(f"{group_name}-聊天记录分析.xlsx", engine="xlsxwriter")
|
||||
chat_messages ,chat_analyzer = parser_chat_text(group_messages)
|
||||
df1 = pd.DataFrame(chat_messages,columns=["user","date","time","content"])
|
||||
df1 = df1.sort_values("date",ascending=False)
|
||||
df2 = pd.DataFrame(chat_analyzer,columns=["user","frequence","word_count"])
|
||||
df2 = df2.sort_values("frequence",ascending=False)
|
||||
df2.to_excel(writer, sheet_name="记录分析",index=False)
|
||||
df1.to_excel(writer, sheet_name="记录详情",index=False)
|
||||
writer.close()
|
BIN
groupchat/创业板群1-聊天记录分析.xlsx
Normal file
BIN
groupchat/创业板群1-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/创业板群2-聊天记录分析.xlsx
Normal file
BIN
groupchat/创业板群2-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/创业板群3-聊天记录分析.xlsx
Normal file
BIN
groupchat/创业板群3-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/创业板群4-聊天记录分析.xlsx
Normal file
BIN
groupchat/创业板群4-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/创业板群5-聊天记录分析.xlsx
Normal file
BIN
groupchat/创业板群5-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/深主板群1-聊天记录分析.xlsx
Normal file
BIN
groupchat/深主板群1-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/深主板群2-聊天记录分析.xlsx
Normal file
BIN
groupchat/深主板群2-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/深主板群3-聊天记录分析.xlsx
Normal file
BIN
groupchat/深主板群3-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/深主板群4-聊天记录分析.xlsx
Normal file
BIN
groupchat/深主板群4-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/深主板群5-聊天记录分析.xlsx
Normal file
BIN
groupchat/深主板群5-聊天记录分析.xlsx
Normal file
Binary file not shown.
BIN
groupchat/聊天记录.xlsx
Normal file
BIN
groupchat/聊天记录.xlsx
Normal file
Binary file not shown.
0
identifier.sqlite
Normal file
0
identifier.sqlite
Normal file
751
index.py
Normal file
751
index.py
Normal file
@@ -0,0 +1,751 @@
|
||||
# !/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
|
||||
import requests
|
||||
|
||||
from access_token import SmartTableApi, transform_data
|
||||
from lib import WXworkAuth, CloudFunctionAPI, WXworkCalendarAPI, parse_data, WXworkApprovalAPI, WXworkUserInfoAPI,WXworkDocAPI
|
||||
|
||||
crop_id = "wx98dfa35ad7e4b271"
|
||||
space_id = "s.wx98dfa35ad7e4b271.744621152iSk"
|
||||
doc_id = 'dc1zao6J8YnS9p86FKKvKQadukmFXlXv3dU1qVQrgcjB81zdSVI7qoi9a7K_OpN4BTyXc8EbYVXxpnQNUUTMWwIw'
|
||||
wxc_app_secret = "gyN5moBa6Ev1Vd0ZLUVrsEtAO_goppWKUBdsnSng-Ks"
|
||||
wapi_app_secret = "oM0hwIi8GRPw6HWk9o5__g3v5ziz5CGyBUo2FASSrVw"
|
||||
wxc_auth = WXworkAuth(crop_id, wxc_app_secret)
|
||||
wapi_auth = WXworkAuth(crop_id, wapi_app_secret)
|
||||
cf_api = CloudFunctionAPI("http://10.0.0.39/cloud")
|
||||
wxc_api = WXworkCalendarAPI(wxc_auth)
|
||||
wapi = WXworkApprovalAPI(wapi_auth)
|
||||
wup = WXworkUserInfoAPI(wapi_auth)
|
||||
ssp = WXworkDocAPI(wxc_auth,space_id)
|
||||
|
||||
|
||||
def file_exists(file_path):
|
||||
_url = f"https://a.cmdp.cn/basiceg/v1/csp/exist/{file_path}"
|
||||
response = requests.get(_url.format(file_path=file_path))
|
||||
return response.json().get("exist", False)
|
||||
|
||||
class CalendarDB:
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.user_id = None
|
||||
self.user_info = None
|
||||
self.calendar_id = None
|
||||
self.calendar_connection = None
|
||||
self.cloud_connection = None
|
||||
|
||||
def login(self, login_user: str):
|
||||
self.name = login_user
|
||||
self.calendar_connection = wxc_api
|
||||
self.cloud_connection = cf_api
|
||||
user_info = self.cloud_connection.get_user_info_by_name(user_name=self.name)
|
||||
self.user_id = user_info.get('id')
|
||||
user_tags = self.cloud_connection.parse_tags(user_info.get('tags', []))
|
||||
calendar_ids = [i for i in user_tags.get('calendar', {}).get('id', {})]
|
||||
self.calendar_id = calendar_ids[0] if calendar_ids else None
|
||||
if not self.calendar_id:
|
||||
self.calendar_id = self.create_calendar("访问日历")
|
||||
if self.calendar_id:
|
||||
self.cloud_connection.update_user_tag(user_id=self.user_id, key="calendar_id", value=self.calendar_id)
|
||||
return {"calendar": self.calendar_id}
|
||||
|
||||
def create_calendar(self, calendar_name: str):
|
||||
return self.calendar_connection.create_calendar(calendar_name, self.name)
|
||||
|
||||
@abstractmethod
|
||||
def _transfer_calendar_data(self, data: dict) -> dict:
|
||||
pass
|
||||
|
||||
def get_all(self):
|
||||
return self.calendar_connection.get_calendar_list(self.calendar_id)
|
||||
|
||||
def add(self, data: dict):
|
||||
_data = data.copy()
|
||||
data.pop("_uid")
|
||||
schedule_name = f"{data.get('company_name')}[{data.get('company_code')}] 拜访计划"
|
||||
start_time = data.get("visit_time")
|
||||
end_time = data.get("end_time")
|
||||
content = json.dumps(data, ensure_ascii=False)
|
||||
address = data.get("company_address")
|
||||
self.calendar_connection.create_schedule(self.calendar_id, schedule_name, start_time, end_time, self.name,
|
||||
content,
|
||||
address)
|
||||
return self.trigger(_data )
|
||||
|
||||
@abstractmethod
|
||||
def trigger(self, data: dict):
|
||||
pass
|
||||
|
||||
def delete(self, schedule_id: str):
|
||||
self.trigger({})
|
||||
pass
|
||||
|
||||
|
||||
def get_report_ticket(file_path: str, download_name: str):
|
||||
"""
|
||||
四.生成文件下载地址生成【通用】
|
||||
说明
|
||||
调用接口获取ticket,然后拼接地址后发给用户
|
||||
接口地址
|
||||
https://a.cmdp.cn/v1/cloudfile/file/createTicket
|
||||
输入参数[post]
|
||||
{
|
||||
"appId": "",
|
||||
"uri": "s3://usercentre03-dev/user/documents/my document/00002rfdata11792529c9f6e136e282b6416ff20d3f.json", //文件的s3地址
|
||||
"startTime": "2024-03-21 12:12:12",
|
||||
"endTime": "",
|
||||
"maxTimes": 0,
|
||||
"uid": "",
|
||||
"name": "1.json", //下载时的文件名
|
||||
"isPublic":false
|
||||
}
|
||||
|
||||
返回示例:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": null,
|
||||
"data": "LCXNDEwla",
|
||||
"validations": null,
|
||||
"token": null,
|
||||
"success": true
|
||||
}
|
||||
拼接文件下载地址
|
||||
https://ea.cmdp.cn/v1/cloudfile/file/?ticket=LCXNDEwla
|
||||
|
||||
"""
|
||||
url = "https://a.cmdp.cn/v1/cloudfile/file/createTicket"
|
||||
body = {
|
||||
"appId": "",
|
||||
"uri": file_path,
|
||||
# 文件的s3地址
|
||||
"startTime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())),
|
||||
"endTime": "",
|
||||
"maxTimes": 0,
|
||||
"uid": "",
|
||||
"name": download_name, # 下载时的文件名
|
||||
"isPublic": False
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(url, json=body, headers=headers)
|
||||
ticket = response.json().get("data")
|
||||
download_url = f"https://a.cmdp.cn/v1/cloudfile/file/?ticket={ticket}"
|
||||
return download_url
|
||||
|
||||
|
||||
class SalesTools:
|
||||
|
||||
@staticmethod
|
||||
def get_user_info(security_code: str):
|
||||
"""
|
||||
二.用户列表查询
|
||||
说明
|
||||
【同步调用】
|
||||
接口地址
|
||||
http://10.0.0.39/cloud/in/api/userQuery
|
||||
|
||||
输入参数[post]
|
||||
{
|
||||
"secCode": "000002",
|
||||
"status":10,
|
||||
"PageSize": 10,
|
||||
"PageNo": 1
|
||||
}"""
|
||||
return cf_api.get_user_info_by_sec(security_code)
|
||||
|
||||
@staticmethod
|
||||
def update_user_tag(user_id: str):
|
||||
"""
|
||||
三.用户打标签
|
||||
说明
|
||||
【同步调用】
|
||||
接口地址
|
||||
|
||||
http://10.0.0.39/cloud/in/api/user
|
||||
输入参数[post]
|
||||
{
|
||||
"id": "1722805021193383937",
|
||||
"tags": [
|
||||
"客户类型_上市公司",
|
||||
"客户权限_动态令牌"
|
||||
]
|
||||
}
|
||||
"""
|
||||
cf_api.update_user_tags(user_id, {"客户类型": "上市公司", "客户权限": "动态令牌"})
|
||||
|
||||
@staticmethod
|
||||
def get_org_info(security_code: str):
|
||||
"""
|
||||
四.通用机构信息查询
|
||||
说明
|
||||
接口地址
|
||||
http://ea.cmdp.cn/apm/v1/ability/api000353
|
||||
|
||||
输入参数[post]
|
||||
{
|
||||
"_data": {
|
||||
"searchType": "stock",
|
||||
"secCode": "000002" //证券代码
|
||||
}
|
||||
}
|
||||
"""
|
||||
url = "http://a.cmdp.cn/apm/v1/ability/api000353"
|
||||
body = {
|
||||
"_data": {
|
||||
"searchType": "stock",
|
||||
"secCode": security_code
|
||||
}
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(url, json=body, headers=headers)
|
||||
return response.json().get("data", {})
|
||||
|
||||
@staticmethod
|
||||
def get_report_rule(tags: [str]):
|
||||
"""
|
||||
|
||||
四.尽调报告的规则文件地址查询
|
||||
说明
|
||||
接口地址
|
||||
https://a.cmdp.cn/cmnd/ndisksvr/ms/v1/dir_list/usys/pbcs/release
|
||||
|
||||
输入参数[post]
|
||||
{
|
||||
"opUserId": "genesys_app",
|
||||
"sourceUserId": "genesys_app",
|
||||
"filter":"'fileInfo.extends.resource.delStatus' eq 1 and 'fileInfo.extends.pbcMeta.name' eq '尽调报告云盘项目名称,需要问@冯昕宇'",【企业售前画像报告】
|
||||
"tags":["报告年度_2023","biz_项目主体类别_单体","biz_业务场所_深交所"]
|
||||
}
|
||||
"""
|
||||
url = "https://a.cmdp.cn/cmnd/ndisksvr/ms/v1/dir_list/usys/pbcs/release"
|
||||
body = {
|
||||
"opUserId": "genesys_app",
|
||||
"sourceUserId": "genesys_app",
|
||||
"filter": f"'fileInfo.extends.resource.delStatus' eq 1 and 'fileInfo.extends.pbcMeta.name' eq '企业售前画像报告'",
|
||||
"tags": tags
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(url, json=body, headers=headers)
|
||||
return response.json()
|
||||
|
||||
@staticmethod
|
||||
def get_report_info(user_id: str, security_code: str, rule_path: str):
|
||||
"""
|
||||
四.尽调报告执行
|
||||
说明
|
||||
执行后报告需要生成ticket的下载地址
|
||||
接口地址
|
||||
https://a.cmdp.cn/v1/cmp/cal/fn/PerformRuleFile
|
||||
输入参数[post]
|
||||
{
|
||||
"issync": 2,
|
||||
"ruleobj": "temp-003/rule.json",
|
||||
"dataobj": {
|
||||
"uid": "用户ID", //填入真实用户ID,执行后会在微信公众收到结果消息,可以直接打开报告
|
||||
"_data": {
|
||||
"secCode": "000002"
|
||||
},
|
||||
"_var": {
|
||||
"config": {
|
||||
"prodName": "企业售前画像报告"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
url = "https://a.cmdp.cn/v1/cmp/cal/fn/PerformRuleFile"
|
||||
body = {
|
||||
"issync": 2, # 同步执行
|
||||
"ruleobj": rule_path,
|
||||
"dataobj": {
|
||||
"uid": user_id,
|
||||
"_data": {
|
||||
"secCode": security_code
|
||||
},
|
||||
"_var": {
|
||||
"config": {
|
||||
"prodName": "企业售前画像报告"
|
||||
}
|
||||
}
|
||||
}}
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(url, json=body, headers=headers)
|
||||
return response.json().get("dumpmsg", {}).get("_out", {})
|
||||
|
||||
@staticmethod
|
||||
def get_regression_testing(eid: str):
|
||||
|
||||
"""
|
||||
执行后报告需要生成ticket的下载地址:2024年半年报
|
||||
@卢天宝说明一下文件存储路径,@常连钰直接根据文件存储路径生成下载ticket,但是需要判断是否存在:https://a.cmdp.cn/basiceg/v1/csp/exist/{path}
|
||||
存储路径:announce02/{textId}._val_01.json , announce02/{textId}._val_01.docx
|
||||
textId获取:
|
||||
请求地址(post):http://a.cmdp.cn/cnd/v1/entity/search/cmdb_bfmapping
|
||||
参数 :
|
||||
{
|
||||
"tags": [
|
||||
"报告期_2024QR1", //替换为真实报告期
|
||||
"GE8002005E_100000000002334460" //GE8002005E_{eid}
|
||||
]
|
||||
}
|
||||
返回示例:
|
||||
{
|
||||
"total": 1,
|
||||
"records": [
|
||||
{
|
||||
"eid": "100000000037315576",
|
||||
"tags": {
|
||||
"公司名称_上海凌云实业发展股份有限公司": true,
|
||||
"GE8002005E_100000000002334460": true,
|
||||
"证券代码_900957": true,
|
||||
"报告期_2024QR1": true,
|
||||
"TEXTID_1219815286": true, //其中1219815286就是text的值
|
||||
"公告标题_凌云B股:凌云B股2024年第一季度报告": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
def get_text_id(report_date, entity_id):
|
||||
url = "https://a.cmdp.cn/cnd/v1/entity/search/cmdb_bfmapping"
|
||||
body = {
|
||||
"tags": [
|
||||
f"报告期_{report_date}",
|
||||
f"GE8002005E_{entity_id}"
|
||||
]
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(url, json=body, headers=headers)
|
||||
result = response.json()
|
||||
tags_ = result.get("records", [{}])
|
||||
if tags_:
|
||||
tags = tags_[0].get("tags", [{}])
|
||||
else:
|
||||
tags = []
|
||||
|
||||
text_id = None
|
||||
for tag in tags:
|
||||
l = tag.split("_")
|
||||
if l[0] == "TEXTID":
|
||||
text_id = l[1]
|
||||
return text_id
|
||||
|
||||
def get_report_datetime():
|
||||
report_datetime = time.localtime(time.time())
|
||||
year = report_datetime.tm_year
|
||||
month = report_datetime.tm_mon
|
||||
if month >= 9:
|
||||
return [f"{year}SAR",f"{year-1}AR"]
|
||||
elif month >= 4:
|
||||
return [f"{year-1}SAR",f"{year-1}AR"]
|
||||
else:
|
||||
return [f"{year-1}SAR",f"{year-2}AR"]
|
||||
|
||||
def get_report(tid):
|
||||
if not tid:
|
||||
return None
|
||||
docx_path = f"announce02/{tid}_val_01.docx"
|
||||
json_path = f"announce02/{tid}_val_01.json"
|
||||
if file_exists(json_path) and file_exists(docx_path):
|
||||
return flag , get_report_ticket("s3://" + docx_path, "回归测试报告.docx")
|
||||
else:
|
||||
print("file not exist")
|
||||
return None, None
|
||||
|
||||
flags = get_report_datetime()
|
||||
result = ""
|
||||
for flag in flags:
|
||||
text_id = get_text_id(flag,eid)
|
||||
f, r = get_report(text_id)
|
||||
if r:
|
||||
result += f'<br><p color="black">{f}:</p><a href="{r}" target="_blank" style="color: rgb(38, 126, 240); text-decoration-line: underline; outline-style: none; cursor: pointer; font-size: 16px; font-weight: 400;">{r}</a>'
|
||||
if result != "":
|
||||
return result
|
||||
else:
|
||||
return "暂无"
|
||||
|
||||
@staticmethod
|
||||
def get_current_product_id():
|
||||
now = time.localtime(time.time())
|
||||
year = now.tm_year
|
||||
month = now.tm_mon
|
||||
if month >= 5:
|
||||
year = year - 1
|
||||
else:
|
||||
year = year - 2
|
||||
url = "https://a.cmdp.cn/cmnd/ndisksvr/ms/v1/dir_listall/usys/products/release"
|
||||
body = {
|
||||
"opUserId": "genesys_app",
|
||||
"sourceUserId": "genesys_app",
|
||||
"filter": "'fileInfo.extends.resource.delStatus' eq 1 and 'fileInfo.extends.resource.label' eq '小智智填-定期报告专版'",
|
||||
"tags": ["tech_sbom", f"prod_year_{year}", "type_入口", "prod_小智转写"]
|
||||
}
|
||||
print(url,json.dumps(body,ensure_ascii=False))
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code == 200:
|
||||
response_json = response.json()
|
||||
code = response_json.get("code", -1)
|
||||
if code == 200:
|
||||
data = response_json.get("data", {})
|
||||
records = data.get("records", [])
|
||||
if records:
|
||||
widget_info = records[0].get("fileInfo", {}).get("extends", {}).get("widget", {})
|
||||
product_id = widget_info.get("id")
|
||||
return product_id
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_smart_genie_product(entity_id: str, product_id: str):
|
||||
"""
|
||||
说明
|
||||
选样本后的订购【打印或复制】小智类产品
|
||||
内部接口,eid和个人uid是输入的。
|
||||
产品选择是输入产品id:
|
||||
产品 产品id 说明
|
||||
小智复核-定期报告专版 已有产品,示例
|
||||
小智智填-定期报告专版 1854436156570607618 产品id待补充
|
||||
|
||||
接口地址
|
||||
https://cloud.cmdp.cn/api/product/deliver/print
|
||||
|
||||
输入参数[post]
|
||||
{
|
||||
"eid": "eid,不填就是查询用户所属公司的",
|
||||
"pid": "必填,入口产品id",
|
||||
"priceType":"产品版本,枚举:体验、标准版、旗舰版、特别优惠版";售前产品固定:标准版
|
||||
}
|
||||
输入示例
|
||||
{
|
||||
"eid": "100000000002334059",
|
||||
"pid": "1858326969193848833",
|
||||
"priceType":"标准版"
|
||||
}
|
||||
返回参数
|
||||
{"Code":"状态码","data":"入口产品id"}
|
||||
返回示例
|
||||
|
||||
{"Code":200,"data":"1853245209740582914"}
|
||||
打印成功后会有公众号通知
|
||||
|
||||
|
||||
"""
|
||||
|
||||
url = "https://cloud.cmdp.cn/api/product/deliver/print"
|
||||
body = {
|
||||
"eid": entity_id,
|
||||
"pid": product_id if product_id else "",
|
||||
"priceType": "体验版"
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code == 200:
|
||||
response_json = response.json()
|
||||
code = response_json.get("code", -1)
|
||||
if code == 200:
|
||||
return response_json.get("data", "")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_product_qrcode(entity_id: str, product_id: str):
|
||||
"""
|
||||
接口示例:
|
||||
标签查fid:
|
||||
curl --location --request POST "http://10.0.0.39/octopus/usr/genesys_app/file/query" ^
|
||||
--header "User-Agent: Apifox/1.0.0 (https://apifox.com)" ^
|
||||
--header "Content-Type: application/json" ^
|
||||
--header "Accept: */*" ^
|
||||
--header "Host: 10.0.0.39" ^
|
||||
--header "Connection: keep-alive" ^
|
||||
--data-raw "{ \"Category\": \"default\", \"ownerID\":\"genesys_app\", \"query\": \"\\\"uid_1652264045856165888\\\"\", \"path\":\"/system/users\", \"includeHistory\":false}"
|
||||
|
||||
fid查文件:
|
||||
curl --location --request GET "http://10.0.0.39/octopus/usr/genesys_app/file/1-23823/meta" ^
|
||||
--header "User-Agent: Apifox/1.0.0 (https://apifox.com)" ^
|
||||
--header "Accept: */*" ^
|
||||
--header "Host: 10.0.0.39" ^
|
||||
--header "Connection: keep-alive"
|
||||
|
||||
query查询标签:
|
||||
查询标签 【"eid_{eid}","pid_{pid}","priceType_标准版"】
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def get_file_by_fid(file_id):
|
||||
url = f"http://10.0.0.39/octopus/usr/genesys_app/file/{file_id}/meta"
|
||||
|
||||
try:
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def get_file_id(entity_id: str, product_id: str):
|
||||
url = "http://10.0.0.39/octopus/usr/genesys_app/file/query"
|
||||
body = {
|
||||
"Category": "default",
|
||||
"ownerID": "genesys_app",
|
||||
"query": f"\"eid_{entity_id}\" & \"pid_{product_id}\" & \"priceType_标准版\"",
|
||||
"path": "/system/users",
|
||||
"includeHistory": False
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return []
|
||||
|
||||
if not product_id:
|
||||
return None
|
||||
file_ids = get_file_id(entity_id, product_id)
|
||||
|
||||
if not file_ids:
|
||||
return None
|
||||
file_info = get_file_by_fid(file_ids[0])
|
||||
|
||||
return file_info.get("uri")
|
||||
|
||||
@staticmethod
|
||||
def send_company_mail(to_address: str, subject: str, content: str):
|
||||
url = "https://a.cmdp.cn/apm/v1/ability/api000357"
|
||||
body = {"_data": {
|
||||
"type": "html",
|
||||
"tos": [to_address],
|
||||
"subject": subject,
|
||||
"text": content
|
||||
}
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
return response.json()
|
||||
|
||||
@staticmethod
|
||||
def get_qrcode_base64(product_qrcode_path):
|
||||
if not product_qrcode_path:
|
||||
return ""
|
||||
response = requests.get(product_qrcode_path)
|
||||
if response.status_code == 200:
|
||||
return base64.b64encode(response.content).decode('utf-8')
|
||||
else:
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def get_information_disclosure_picture(year:str,security_code:str):
|
||||
basiceg_url = "http://a.cmdp.cn/basiceg/v1/bytes"
|
||||
file_path = f"/usercentre03-dev/user/cache/{year}/信披成绩单/{security_code}_idsr.jpg"
|
||||
static_path = f"/product-data/static/xpcjd/{security_code}_idsr.jpg"
|
||||
if file_exists(file_path):
|
||||
resp = requests.get(basiceg_url+file_path)
|
||||
if resp.status_code == 200:
|
||||
data = resp.content
|
||||
resp = requests.put(basiceg_url+static_path,data=data)
|
||||
return f'<img src="http://www.cmdp.cn/a/s/xpcjd/{security_code}_idsr.jpg" width="100%">'
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
st = SalesTools()
|
||||
|
||||
|
||||
class CalendarEventDB(CalendarDB):
|
||||
|
||||
def _transfer_calendar_data(self, data: dict) -> dict:
|
||||
pass
|
||||
|
||||
def trigger(self, data: dict):
|
||||
security_code = data.get("company_code")
|
||||
user_list = st.get_user_info(security_code)
|
||||
# 获取公司信息
|
||||
company_info = st.get_org_info(security_code)
|
||||
eid = company_info.get("eid")
|
||||
full_name = company_info.get("fullName")
|
||||
# 打标签
|
||||
if user_list:
|
||||
for user in user_list:
|
||||
user_id = user.get("id")
|
||||
st.update_user_tag(user_id)
|
||||
# 获取bp id
|
||||
bp_info = self.cloud_connection.get_user_info_by_name(self.name)
|
||||
bp_id = bp_info.get("id")
|
||||
bp_mail = bp_info.get("email")
|
||||
|
||||
# 获取二维码
|
||||
# product_id = st.get_current_product_id()
|
||||
# product_id ="1909049700811497474"
|
||||
# st.get_smart_genie_product(eid, product_id)
|
||||
# product_qrcode_path = st.get_product_qrcode(eid, product_id)
|
||||
# qrcode = st.get_qrcode_base64(product_qrcode_path)
|
||||
# 获取报告文件
|
||||
now = time.localtime(time.time())
|
||||
year = now.tm_year
|
||||
month = now.tm_mon
|
||||
if month >= 5:
|
||||
year = year - 1
|
||||
else:
|
||||
year = year - 2
|
||||
report_tags = [f"报告年度_{year}", "biz_项目主体类别_单体",
|
||||
"biz_业务场所_深交所"]
|
||||
report_rule_info = st.get_report_rule(report_tags)
|
||||
report_rule_path = report_rule_info.get("data", {}).get("records", "")
|
||||
if report_rule_path:
|
||||
report_rule_path = report_rule_path[0].get("filePath", "")
|
||||
report_info = st.get_report_info(bp_id, security_code, report_rule_path)
|
||||
report_ticket = report_info.get("data").get("path")
|
||||
regression_testing = st.get_regression_testing(eid)
|
||||
with open("template.html", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
content = content.replace("{{report_ticket}}", report_ticket)
|
||||
content = content.replace("{{regression_testing}}", regression_testing)
|
||||
# content = content.replace("{{qrcode}}", qrcode)
|
||||
content = content.replace("{{picture}}",st.get_information_disclosure_picture("2024",security_code))
|
||||
st.send_company_mail(bp_mail, f"客户拜访准备文件-{full_name}[{security_code}]", content)
|
||||
ssp.add_row(doc_id=doc_id,sheet_id="tTMuVB",records=[
|
||||
{
|
||||
"values": {
|
||||
"证券代码": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": data["company_code"]
|
||||
}
|
||||
],
|
||||
"证券简称": [{
|
||||
"type": "text",
|
||||
"text": data["company_name"]
|
||||
}]
|
||||
,
|
||||
"地点": [{
|
||||
"type": "text",
|
||||
"text": data["company_address"]
|
||||
}]
|
||||
,
|
||||
"拜访时间": data["visit_time"]+"000"
|
||||
,
|
||||
"结束时间": data["end_time"]+"000",
|
||||
"拜访人": [{
|
||||
"type": "text",
|
||||
"text": data["_uid"]
|
||||
}]
|
||||
|
||||
|
||||
}
|
||||
}])
|
||||
|
||||
|
||||
ceb = CalendarEventDB()
|
||||
|
||||
|
||||
def get_approval(template_id):
|
||||
approval_list = wapi.get_approval_list_weekly(template_id)
|
||||
info = {}
|
||||
for approval_id in approval_list:
|
||||
approval_info = wapi.get_approval_info(approval_id)
|
||||
approval_info = parse_data(approval_info["info"],
|
||||
{"Number-1697103952657": "company_code", "Text-1697103919621": "company_name",
|
||||
"Text-1728374569134": "company_address", "Date-1728370374837": "visit_time",
|
||||
"Date-1728370485478": "end_time"})
|
||||
_uid = approval_info["_uid"]
|
||||
if _uid not in info:
|
||||
_u = wup.get_user_info(_uid)
|
||||
info[_uid] = {"name": _u["name"]}
|
||||
else:
|
||||
_p = info[_uid]
|
||||
company_code = approval_info["company_code"]
|
||||
if company_code not in _p:
|
||||
_p[company_code] = 1
|
||||
else:
|
||||
_p[company_code] += 1
|
||||
return info
|
||||
|
||||
|
||||
def generate_approval_report_html(times_report):
|
||||
htms = "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><title></title><style>table{width:100%;border-collapse:collapse;font-family:Arial,sans-serif;box-shadow:0 2px 10px rgba(0,0,0,0.1)}caption{font-size:2em;font-weight:bold;margin:1em 0}th,td{border:1px solid#ccc;text-align:center;padding:15px;transition:background-color 0.3s}thead tr{background-color:#007BFF;color:#fff}tbody tr:nth-child(odd){background-color:#f9f9f9}tbody tr:hover{background-color:#e2e6ea}tfoot tr td{text-align:right;padding-right:20px}th{border-bottom:2px solid#007BFF}</style> </head><body><table border='1'><tr><th>姓名</th><th>公司代码</th><th>次数</th></tr>"
|
||||
htme = "</table></body></html>"
|
||||
content = ""
|
||||
for key in times_report:
|
||||
key_report = times_report[key]
|
||||
name = key_report["name"]
|
||||
key_report_list = [(i, key_report[i]) for i in key_report if i != "name"]
|
||||
size = len(key_report_list)
|
||||
for idx, key_info in enumerate(key_report_list):
|
||||
if idx == 0:
|
||||
content += f"<tr><td rowspan='{size}'>{name}</td><td>{key_info[0]}</td><td>{key_info[1]}</td></tr>"
|
||||
else:
|
||||
content += f"<tr><td>{key_info[0]}</td><td>{key_info[1]}</td></tr>"
|
||||
return htms + content + htme
|
||||
|
||||
|
||||
def new_product_print(info):
|
||||
url = "https://cloud.cmdp.cn/api/product/print/customize"
|
||||
body = {"userId": info["user_id"], "secCode": info["company_code"], "prodName": info["service_name"],
|
||||
"opUserId": info["_uid"]}
|
||||
r = requests.post(url, json=body)
|
||||
return r.json()
|
||||
|
||||
|
||||
|
||||
|
||||
def main_handler(event, context):
|
||||
data = json.loads(event.get("body"))
|
||||
template_id = data.get("template_id", "C4ZT8ZuPRbKC76KmWCrmRAKyVdneoq9wh1ESaUrrJ")
|
||||
if data.get("action") == "get_approval":
|
||||
result = get_approval(template_id)
|
||||
result = generate_approval_report_html(result)
|
||||
address = data.get("address", "lychang@genesysinfo.net")
|
||||
st.send_company_mail(address, "客户拜访审批次数报表", result)
|
||||
return {"code": 200, "msg": "success"}
|
||||
elif data.get("action") == "otp":
|
||||
data_list = data.get("data",[{}])
|
||||
sta = SmartTableApi("dcqzSDHPl2ZWoLkEB3Id1wMMaW0meMSwGLjfnuHD0VMh0DVidKdal-3wYfsh7Zb1pGn9moDuzjvhtlRZAXoO-Hjg")
|
||||
mapping = {
|
||||
"userName": "用户名称",
|
||||
"secCode": "公司代码",
|
||||
"companyName": "公司名称",
|
||||
"count": "使用动态口令次数",
|
||||
"date": "时间",
|
||||
"goodName": "产品"
|
||||
}
|
||||
data_list = [transform_data(i, mapping) for i in data_list]
|
||||
cursor = sta.get_sheet("g6dTI2")
|
||||
if cursor.batch_add(data_list):
|
||||
return {"code": 200, "msg": "success"}
|
||||
else:
|
||||
return {"code": 500, "msg": "error"}
|
||||
|
||||
elif data.get("action") == "ip":
|
||||
resp = requests.get("http://cip.cc")
|
||||
return resp.text
|
||||
elif template_id == "C4ZT8ZuPRbKC76KmWCrmRAKyVdneoq9wh1ESaUrrJ":
|
||||
info = parse_data(data, {"Number-1697103952657": "company_code", "Text-1697103919621": "company_name",
|
||||
"Text-1728374569134": "company_address", "Date-1728370374837": "visit_time",
|
||||
"Date-1728370485478": "end_time"})
|
||||
ceb.login(info["_uid"])
|
||||
result = ceb.add(info)
|
||||
return {"code": 200, "msg": "success"}
|
||||
elif template_id == "3WMWPcxrt28TRT72wqH74KSjMDBhmZPJguv1VEZZ":
|
||||
info = parse_data(data, {
|
||||
"Selector-1688785038239": "service_name",
|
||||
"Number-1661195930200": "company_code",
|
||||
"Number-1727161672205": "user_id"
|
||||
})
|
||||
new_product_print(info)
|
||||
return {"code": 200, "msg": "success"}
|
||||
else:
|
||||
return {"code": 400, "msg": "not found"}
|
||||
|
516
lib.py
Normal file
516
lib.py
Normal file
@@ -0,0 +1,516 @@
|
||||
# !/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
class WXworkAuth:
|
||||
def __init__(self, app_id, app_key):
|
||||
self.crop_id = app_id
|
||||
self.crop_secret = app_key
|
||||
self.access_token = None
|
||||
self.timestamp = None
|
||||
|
||||
def _get_access_token(self):
|
||||
self.timestamp = int(time.time())
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.crop_id}&corpsecret={self.crop_secret}"
|
||||
response = requests.get(url)
|
||||
access_info = response.json()
|
||||
if 'access_token' in access_info:
|
||||
self.access_token = access_info['access_token']
|
||||
else:
|
||||
raise Exception('获取access_token失败')
|
||||
|
||||
def get_access_token(self):
|
||||
_now = int(time.time())
|
||||
if not self.access_token or (_now - self.timestamp) > 7200:
|
||||
self.refresh_token()
|
||||
return self.access_token
|
||||
|
||||
def refresh_token(self):
|
||||
try:
|
||||
self._get_access_token()
|
||||
except Exception as e:
|
||||
print(e, '重新获取')
|
||||
self._get_access_token()
|
||||
|
||||
|
||||
class CloudFunctionAPI:
|
||||
def __init__(self, base_url):
|
||||
self.base_url = base_url
|
||||
|
||||
def get_user_info_by_id(self, user_id: str) -> dict:
|
||||
url = f"{self.base_url}/in/api/user"
|
||||
body = {
|
||||
"uid": user_id
|
||||
|
||||
}
|
||||
response = requests.get(url, params=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
user_info = response.json().get('data', {})
|
||||
return user_info
|
||||
|
||||
def get_user_info_by_sec(self, security_code: str) -> list:
|
||||
url = f"{self.base_url}/in/api/userQuery"
|
||||
body = {
|
||||
"secCode": security_code,
|
||||
"status": 10,
|
||||
"PageSize": 10,
|
||||
"PageNo": 1
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
|
||||
user_info = response.json().get("list")
|
||||
if user_info is None:
|
||||
user_info = [{}]
|
||||
return user_info
|
||||
|
||||
def get_user_info_by_name(self, user_name: str) -> dict:
|
||||
url = f"{self.base_url}/in/api/userQuery"
|
||||
body = {
|
||||
"loginName": user_name,
|
||||
"status": 10,
|
||||
"PageSize": 10,
|
||||
"PageNo": 1
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
user_info = response.json().get("list")
|
||||
if user_info is None:
|
||||
user_info = {}
|
||||
else:
|
||||
user_info = user_info[0]
|
||||
return user_info
|
||||
|
||||
@staticmethod
|
||||
def parse_tags(tags: list):
|
||||
def _parse_tag(_tag: str):
|
||||
return _tag.split("_")
|
||||
|
||||
temp_dict = {}
|
||||
for tag in tags:
|
||||
tag_split = _parse_tag(tag)
|
||||
size = len(tag_split)
|
||||
cursor = None
|
||||
for idx, i in enumerate(tag_split):
|
||||
if idx == 0:
|
||||
cursor = temp_dict
|
||||
val = {} if idx < size - 1 else True
|
||||
cursor[i] = val
|
||||
if isinstance(val,bool):
|
||||
cursor = None
|
||||
else:
|
||||
cursor = cursor[i]
|
||||
print(temp_dict)
|
||||
return temp_dict
|
||||
|
||||
def get_user_tags(self, user_id: str = "", user_name: str = "") -> dict:
|
||||
if user_id:
|
||||
user_info = self.get_user_info_by_id(user_id)
|
||||
elif user_name:
|
||||
user_info = self.get_user_info_by_name(user_name)
|
||||
else:
|
||||
return {}
|
||||
tags = user_info.get('tags', [])
|
||||
return self.parse_tags(tags)
|
||||
|
||||
def update_user_tag(self, user_id: str, key: str, value: str):
|
||||
url = f"{self.base_url}/in/api/userTags"
|
||||
body = {
|
||||
"userId": user_id,
|
||||
"tags": [f"{key}_{value}"]
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
user_info = response.json()
|
||||
return user_info
|
||||
|
||||
def update_user_tag_by_name(self, user_name: str, key: str, value: str):
|
||||
user_info = self.get_user_info_by_name(user_name)
|
||||
user_id = user_info.get('id')
|
||||
if not user_id:
|
||||
return {}
|
||||
return self.update_user_tag(user_id, key, value)
|
||||
|
||||
def update_user_tags(self, user_id: str, tags: dict):
|
||||
url = f"{self.base_url}/in/api/userTags"
|
||||
body = {
|
||||
"userId": user_id,
|
||||
"tags": [f"{key}_{value}" for key, value in tags.items()]
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
user_info = response.json()
|
||||
return user_info
|
||||
|
||||
|
||||
class WXworkBaseAPI:
|
||||
def __init__(self, auth: WXworkAuth):
|
||||
self.auth = auth
|
||||
|
||||
@property
|
||||
def access_token(self):
|
||||
return self.auth.get_access_token()
|
||||
|
||||
|
||||
class WXworkApprovalAPI(WXworkBaseAPI):
|
||||
def get_approval_list(self, template_id: str, start_time: int, end_time: int) -> list:
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovalinfo?access_token={self.access_token}"
|
||||
body = {
|
||||
"starttime": str(start_time),
|
||||
"endtime": str(end_time),
|
||||
"new_cursor": "",
|
||||
"size": 100,
|
||||
"filters": [
|
||||
{
|
||||
"key": "template_id",
|
||||
"value": template_id
|
||||
},
|
||||
{
|
||||
"key": "sp_status",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return []
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info["sp_no_list"]
|
||||
|
||||
def get_approval_info(self, approval_id: str) -> dict:
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token={self.access_token}"
|
||||
body = {
|
||||
"sp_no": approval_id
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def get_approval_list_weekly(self, template_id) -> list:
|
||||
current_time = time.time()
|
||||
|
||||
# 获取当前时间的结构体
|
||||
current_local_time = time.localtime(current_time)
|
||||
|
||||
# 星期几(0=星期一,6=星期天)
|
||||
weekday = current_local_time.tm_wday
|
||||
|
||||
# 计算下一个星期天的天数
|
||||
days_until_sunday = (6 - weekday) % 7
|
||||
|
||||
this_weekday = int(current_time + (days_until_sunday * 86400))
|
||||
last_weekday = int(this_weekday - 7 * 24 * 3600)
|
||||
return self.get_approval_list(template_id, last_weekday, this_weekday)
|
||||
|
||||
|
||||
class WXworkCalendarAPI(WXworkBaseAPI):
|
||||
|
||||
def create_calendar(self, calendar_name, userid):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/add?access_token={self.access_token}"
|
||||
body = {
|
||||
"calendar": {
|
||||
"admins": [
|
||||
userid
|
||||
],
|
||||
"set_as_default": 1,
|
||||
"summary": calendar_name,
|
||||
"color": "#FF3030",
|
||||
"description": "访问日历",
|
||||
"is_public": 0,
|
||||
"is_corp_calendar": 0,
|
||||
"shares": [
|
||||
{
|
||||
"userid": userid,
|
||||
"permission": 1
|
||||
},
|
||||
],
|
||||
|
||||
}
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
cinfo = response.json()
|
||||
if "cal_id" in cinfo:
|
||||
return cinfo["cal_id"]
|
||||
else:
|
||||
return cinfo
|
||||
|
||||
|
||||
def delete_calendar(self, calendar_id):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/del?access_token={self.access_token}"
|
||||
body = {
|
||||
"cal_id": calendar_id
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
calendar_info = response.json()
|
||||
return calendar_info
|
||||
|
||||
def get_calendar_list(self, calendar_id):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/get_by_calendar?access_token={self.access_token}"
|
||||
body = {
|
||||
"cal_id": calendar_id,
|
||||
"offset": 0,
|
||||
"limit": 1000
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
calendar_list = response.json()
|
||||
return calendar_list
|
||||
|
||||
def create_schedule(self, calendar_id, schedule_name, start_time, end_time, user_id, content, address=""):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/add?access_token={self.access_token}"
|
||||
if start_time == end_time:
|
||||
end_time = str(int(start_time) + 1)
|
||||
body = {
|
||||
"schedule": {
|
||||
"admins": [
|
||||
user_id
|
||||
],
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
"is_whole_day": 0,
|
||||
"attendees": [{
|
||||
"userid": user_id
|
||||
}],
|
||||
"summary": schedule_name,
|
||||
"description": content,
|
||||
"reminders": {
|
||||
"is_remind": 1,
|
||||
"remind_before_event_secs": 3600,
|
||||
"timezone": 8
|
||||
},
|
||||
"location": address,
|
||||
"cal_id": calendar_id
|
||||
}
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
schedule_info = response.json()
|
||||
return schedule_info
|
||||
|
||||
def get_schedule(self, schedule_id):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/get?access_token={self.access_token}"
|
||||
body = {
|
||||
"schedule_id_list": [
|
||||
schedule_id
|
||||
]
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
schedule_list = response.json()
|
||||
return schedule_list
|
||||
|
||||
def delete_schedule(self, schedule_id):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/oa/schedule/del?access_token={self.access_token}"
|
||||
body = {
|
||||
"schedule_id": schedule_id
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
schedule_info = response.json()
|
||||
return schedule_info
|
||||
|
||||
class WXworkUserInfoAPI(WXworkBaseAPI):
|
||||
def get_user_info(self, user_id):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token={self.access_token}&userid={user_id}"
|
||||
response = requests.get(url)
|
||||
user_info = response.json()
|
||||
return user_info
|
||||
|
||||
class WXworkDocAPI:
|
||||
def __init__(self, auth:WXworkAuth,space_id):
|
||||
self.space_id = space_id
|
||||
self.auth = auth
|
||||
self.access_token = self.auth.get_access_token()
|
||||
|
||||
def create_space(self, space_name):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_create?access_token={self.access_token}"
|
||||
body = {
|
||||
"space_name": space_name,
|
||||
"auth_info": [{
|
||||
"type": 1,
|
||||
"userid": "lychang",
|
||||
"auth": 7
|
||||
}],
|
||||
"space_sub_type": 0
|
||||
}
|
||||
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def get_space_info(self):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedrive/space_info?access_token={self.access_token}"
|
||||
body = {
|
||||
"spaceid": self.space_id
|
||||
}
|
||||
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def get_space_file_list(self):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedrive/file_list?access_token={self.access_token}"
|
||||
body = {
|
||||
"spaceid": self.space_id,
|
||||
"fatherid": self.space_id,
|
||||
"sort_type": 1,
|
||||
"start": 0,
|
||||
"limit": 100
|
||||
}
|
||||
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def get_document_info(self,doc_id):
|
||||
self.doc_id = doc_id
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedoc/document/get?access_token={self.access_token}"
|
||||
body = {
|
||||
"docid": self.doc_id
|
||||
}
|
||||
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def get_table_info(self,doc_id, sheet_id, view_ids: list):
|
||||
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/get_views?access_token={self.access_token}"
|
||||
body = {
|
||||
"docid": doc_id,
|
||||
"sheet_id": sheet_id,
|
||||
"view_ids": view_ids,
|
||||
"offset": 0,
|
||||
"limit": 1
|
||||
}
|
||||
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def get_table_data(self, doc_id, sheet_id, view_id):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/get_records?access_token={self.access_token}"
|
||||
body = {
|
||||
"docid": doc_id,
|
||||
"sheet_id": sheet_id,
|
||||
"view_id": view_id,
|
||||
"record_ids": [],
|
||||
"key_type": "CELL_VALUE_KEY_TYPE_FIELD_TITLE",
|
||||
"field_titles": [],
|
||||
"field_ids": [],
|
||||
"sort": [],
|
||||
"offset": 0,
|
||||
"limit": 100
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def get_sheet_data(self, doc_id):
|
||||
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/get_sheet?access_token={self.access_token}"
|
||||
body = {
|
||||
"docid": doc_id
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def create_table(self, doc_name, user_ids: list, father_id=None):
|
||||
"""
|
||||
doc_type 文档类型, 3:文档 4:表格 10:智能表格
|
||||
"""
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedoc/create_doc?access_token={self.access_token}"
|
||||
body = {
|
||||
"doc_type": 10,
|
||||
"doc_name": doc_name,
|
||||
"admin_users": user_ids
|
||||
}
|
||||
if self.space_id:
|
||||
body["spaceid"] = self.space_id
|
||||
if father_id:
|
||||
body["fatherid"] = father_id
|
||||
else:
|
||||
body["fatherid"] = self.space_id
|
||||
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
def add_row(self,doc_id,sheet_id,records:list):
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/wedoc/smartsheet/add_records?access_token={self.access_token}"
|
||||
body = {
|
||||
"docid": doc_id,
|
||||
"sheet_id": sheet_id,
|
||||
"key_type": "CELL_VALUE_KEY_TYPE_FIELD_TITLE",
|
||||
"records": records
|
||||
}
|
||||
response = requests.post(url, json=body)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
else:
|
||||
approval_info = response.json()
|
||||
return approval_info
|
||||
|
||||
|
||||
def parse_data(data: dict, mapping: dict) -> dict:
|
||||
user_id = data.get("applyer").get("userid")
|
||||
app_data = data.get("apply_data").get("contents")
|
||||
_info = {}
|
||||
for i in app_data:
|
||||
key = i.get("title")[0]["text"]
|
||||
cp_id = i.get("id")
|
||||
control = i.get("control")
|
||||
value = i.get("value")
|
||||
if control == "Text":
|
||||
result = value.get("text")
|
||||
elif control == "Number":
|
||||
result = value.get("new_number")
|
||||
elif control == "Date":
|
||||
result = value.get("date").get("s_timestamp")
|
||||
elif control == "Selector":
|
||||
_t = value.get("selector").get("type")
|
||||
if _t == "multi":
|
||||
opts = value.get("selector").get("options",[])
|
||||
result = [j["key"] for j in opts]
|
||||
else:
|
||||
opts = i.get("selector").get("options", [])
|
||||
result = opts[0]["key"]
|
||||
else:
|
||||
result = ""
|
||||
_info[cp_id] = {"key": key, "value": result}
|
||||
_r = {v: _info.get(k, {}).get("value") for k, v in mapping.items()}
|
||||
_r["_uid"] = user_id
|
||||
return _r
|
||||
|
62743
package.json
Normal file
62743
package.json
Normal file
File diff suppressed because it is too large
Load Diff
33
serverless.yml
Normal file
33
serverless.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
app: demo
|
||||
stage: dev
|
||||
|
||||
name: scf-python
|
||||
component: scf
|
||||
|
||||
inputs:
|
||||
src: ./
|
||||
name: ${name}-${stage}-${app} # 云函数名称,默认为 ${name}-${stage}-${app}
|
||||
description: Calendar API Serverless Function
|
||||
handler: index.main_handler
|
||||
runtime: Python3.6
|
||||
namespace: default
|
||||
region: ap-guangzhou
|
||||
type: event # 函数类型,默认为 event(事件类型),web(web类型)
|
||||
memorySize: 128 # 内存大小,单位MB
|
||||
timeout: 900 # 函数执行超时时间,单位秒
|
||||
initTimeout: 300 # 初始化超时时间,单位秒
|
||||
|
||||
vpcConfig: # 私有网络配置
|
||||
vpcId: vpc-q1m7ajxh # 私有网络的Id
|
||||
subnetId: subnet-9ehoo5vi # 子网ID
|
||||
|
||||
events: # 触发器
|
||||
- http:
|
||||
parameters:
|
||||
qualifier: $LATEST # 别名配置
|
||||
enable: true # 是否启用此触发器
|
||||
timeout: 300 # 触发器超时时间,单位秒
|
||||
authType: NONE # 身份认证
|
||||
netConfig:
|
||||
enableExtranet: true # 开启外网访问
|
||||
enableIntranet: true # 开启内网访问
|
20
st/a.py
Normal file
20
st/a.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import pandas as pd
|
||||
|
||||
if __name__ == '__main__':
|
||||
df = pd.read_excel("result1.xlsx",dtype="object")
|
||||
df.fillna("", inplace=True)
|
||||
data = []
|
||||
for i in df.values:
|
||||
cnt = ""
|
||||
for j in i[4].split("\n"):
|
||||
content = j.replace("nan"," ")
|
||||
content = content.replace(f"母公司代码为{i[0]}的公司",f"{i[1]}")
|
||||
content= content.replace("用户类型: 付费, 三级",f"- 母公司名称: {i[1]}")
|
||||
cnt += content + "\n"
|
||||
if "用户类型" in cnt:
|
||||
print(cnt)
|
||||
i[4] = cnt
|
||||
data.append(i)
|
||||
|
||||
df2 = pd.DataFrame(data,columns=df.columns)
|
||||
df2.to_excel("result1.xlsx",index=False)
|
BIN
st/amd.xlsx
Normal file
BIN
st/amd.xlsx
Normal file
Binary file not shown.
23
st/hunyuan.py
Normal file
23
st/hunyuan.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from openai import OpenAI
|
||||
|
||||
if __name__ == '__main__':
|
||||
def chat(message):
|
||||
# 构造 client
|
||||
client = OpenAI(
|
||||
api_key="sk-2NpKKyU5savpy6xQ4pEV0Bv63xCnSdsfnrDcFg9FXlwTjovD", # 混元 APIKey
|
||||
base_url="https://api.hunyuan.cloud.tencent.com/v1", # 混元 endpoint
|
||||
)
|
||||
|
||||
completion = client.chat.completions.create(
|
||||
model="hunyuan-turbos-latest",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": message
|
||||
}
|
||||
],
|
||||
extra_body={
|
||||
"enable_enhancement": True, # <- 自定义参数
|
||||
},
|
||||
)
|
||||
return completion.choices[0].message.content
|
BIN
st/result.xlsx
Normal file
BIN
st/result.xlsx
Normal file
Binary file not shown.
BIN
st/result【re】.xlsx
Normal file
BIN
st/result【re】.xlsx
Normal file
Binary file not shown.
70
st/st.py
Normal file
70
st/st.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import pandas as pd
|
||||
from openai import OpenAI
|
||||
|
||||
|
||||
def chat(message):
|
||||
# 构造 client
|
||||
client = OpenAI(
|
||||
api_key="3N7zycBwsadKjvXBZeAZFVc1VnUJ7yLbbFie5zan5kleBrkZ9BzZkhkFjk2KxZUxs", # 混元 APIKey
|
||||
base_url="https://api.stepfun.com/v1"
|
||||
)
|
||||
|
||||
completion = client.chat.completions.create(
|
||||
model="step-2-mini",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": message
|
||||
}
|
||||
],
|
||||
extra_body={
|
||||
"enable_enhancement": True, # <- 自定义参数
|
||||
},
|
||||
)
|
||||
return completion.choices[0].message.content
|
||||
if __name__ == '__main__':
|
||||
df = pd.read_excel("公告类别基本资料(2025-1-3)【深主板】.xls",dtype="object")
|
||||
df.fillna("", inplace=True)
|
||||
resource = ""
|
||||
notice = ""
|
||||
result = []
|
||||
output = ""
|
||||
temp = ["", "", "", "", "", "", "", ""]
|
||||
try:
|
||||
for i in df.values:
|
||||
|
||||
a,b,c,d,e,f,g,h =i
|
||||
|
||||
if f != "":
|
||||
if temp[5] == "":
|
||||
temp[4] = e
|
||||
temp[5] = f
|
||||
if f != temp[5]:
|
||||
temp[6] = resource
|
||||
temp[7] = notice
|
||||
prompt = "以下内容是上市公司做信息披露时的要点信息,在尽量小幅修改内容和保留引用文件完整名称的情况下,请将其内容转换成markdown格式,综合使用标题设置、多彩字体颜色、字体加粗和生动icon等方式来进行格式化设置,突出重点提示内容,方便用户阅读。只返回markdown内容。\n" + notice
|
||||
# temp[7] = chat(prompt).replace("```markdown\n", "").replace("```", "")
|
||||
|
||||
result.append(temp.copy())
|
||||
|
||||
temp[4] = e
|
||||
temp[5] = f
|
||||
resource = ""
|
||||
notice = ""
|
||||
|
||||
if b !="":
|
||||
temp[0] = a
|
||||
temp[1] = b
|
||||
|
||||
if d !="":
|
||||
temp[2] = c
|
||||
temp[3] = d
|
||||
|
||||
|
||||
resource += g+"\n" if g else ""
|
||||
notice += h+"\n" if h else ""
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
df = pd.DataFrame(result,columns=["公告大类编码","公告大类名称","公告中类编码","公告中类名称","公告子类编码","公告子类名称","报批材料","披露要点"])
|
||||
df.to_excel("c.xlsx")
|
4095
st/公告类别基本资料(2025-1-3)【创业板】.md
Normal file
4095
st/公告类别基本资料(2025-1-3)【创业板】.md
Normal file
File diff suppressed because it is too large
Load Diff
BIN
st/公告类别基本资料(2025-1-3)【创业板】.xls
Normal file
BIN
st/公告类别基本资料(2025-1-3)【创业板】.xls
Normal file
Binary file not shown.
BIN
st/公告类别基本资料(2025-1-3)【创业板】.xlsx
Normal file
BIN
st/公告类别基本资料(2025-1-3)【创业板】.xlsx
Normal file
Binary file not shown.
BIN
st/公告类别基本资料(2025-1-3)【深主板】.xls
Normal file
BIN
st/公告类别基本资料(2025-1-3)【深主板】.xls
Normal file
Binary file not shown.
BIN
st/公告类别基本资料(2025-1-3)【深主板】.xlsx
Normal file
BIN
st/公告类别基本资料(2025-1-3)【深主板】.xlsx
Normal file
Binary file not shown.
BIN
st/公告类别基本资料(2025-1-3)【深办板】.xlsx
Normal file
BIN
st/公告类别基本资料(2025-1-3)【深办板】.xlsx
Normal file
Binary file not shown.
BIN
st/公告类别基本资料(创业板).xlsx
Normal file
BIN
st/公告类别基本资料(创业板).xlsx
Normal file
Binary file not shown.
BIN
st/公告类别基本资料(深主板).xlsx
Normal file
BIN
st/公告类别基本资料(深主板).xlsx
Normal file
Binary file not shown.
BIN
st/深市上市公司环境信息披露样例数据0812.xlsx
Normal file
BIN
st/深市上市公司环境信息披露样例数据0812.xlsx
Normal file
Binary file not shown.
175
template.html
Normal file
175
template.html
Normal file
@@ -0,0 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset=UTF-8>
|
||||
<style>
|
||||
h4 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.headerLineTitle {
|
||||
width: 1.5in;
|
||||
display: inline-block;
|
||||
margin: 0 0 0.0001pt;
|
||||
font-size: 11pt;
|
||||
font-family: "Calibri", "sans-serif";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.headerLineText {
|
||||
display: inline;
|
||||
margin: 0 0 0.0001pt;
|
||||
font-size: 11pt;
|
||||
font-family: "Calibri", "sans-serif";
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.pageHeader {
|
||||
font-size: 14pt;
|
||||
font-family: "Calibri", "sans-serif";
|
||||
font-weight: bold;
|
||||
visibility: visible;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<title>email</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="font-size: 15px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei',serif;"
|
||||
data-mail-from="wemail-pc">
|
||||
<div><span style="background-color: rgb(255, 255, 255);"><spanstyle="font-size:
|
||||
11pt;"> 尊敬的xxx</span></span></div>
|
||||
<div><span style="background-color: rgb(255, 255, 255);"><span style="font-size: 11pt;"><br></span></span></div>
|
||||
<div style="margin: 0;padding: 0;position: relative;">
|
||||
<div style="margin-bottom:0; padding-bottom:0;">
|
||||
<div style="margin: 0;padding-bottom:0;">
|
||||
<div style="margin-bottom:0; padding-bottom:0;">
|
||||
<div style="margin: 0 10px;padding-bottom:0;">
|
||||
<div style="position: relative; margin-bottom:0; line-height: 1.859;"><span
|
||||
style="background-color: rgb(255, 255, 255);"> <span
|
||||
style="font-size: 11pt; background-color: rgb(255, 255, 255);">您好!我是致远速联的【具体人员姓名】。很高兴即将与您交流见面。如今交易所的监管愈发的严格,信息披露合规风险的管控愈发关键。</span></span><span
|
||||
style="font-size: 11pt;">我们深知您对于这方面的重视,因此此次交流旨在与您</span><span
|
||||
style="font-size: 11pt; color: rgb(38, 126, 240);">探讨如何巧妙运用技术手段达成有效的风险规避</span><span
|
||||
style="font-size: 11pt;">。期望能为您带来有价值的信息和解决方案!</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin: 0;padding: 0;position: relative;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0;padding-bottom:0; padding-top:0;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0 10px;padding-bottom:0; padding-top:0;">
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 11pt; line-height: 1.859; background-color: transparent; color: rgb(0, 0, 0);">
|
||||
<span
|
||||
style="background-color: transparent; font-family: 'Microsoft YaHei',serif; font-size: 11pt; color: rgb(0, 0, 0);"><span
|
||||
style="font-size: 11pt; background-color: transparent; font-family: 'Microsoft YaHei',serif; color: rgb(0, 0, 0);"><span
|
||||
style="font-size: 11pt; background-color: rgb(255, 255, 255); font-family: 'Microsoft YaHei',serif; font-weight: normal; font-style: normal; text-decoration-line: none; color: rgb(0, 0, 0);"><span
|
||||
style="background-color: rgb(255, 255, 255);">
|
||||
</span>在正式拜访您之前,我们已经提前准备了相关资料供您查看,以方便您更好的了解我们的产品。如果您在下载的过程中遇到任何问题,请随时与我们联系。我们的客服团队将竭诚为您服务。感谢您对我们公司的信任与支持!</span></span></span>
|
||||
</div>
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 11pt; line-height: 1.859; background-color: transparent; color: rgb(0, 0, 0);">
|
||||
<span
|
||||
style="background-color: transparent; font-family: 'Microsoft YaHei',serif; font-size: 11pt; color: rgb(0, 0, 0);"><span
|
||||
style="font-size: 11pt; background-color: transparent; font-family: 'Microsoft YaHei',serif; color: rgb(0, 0, 0);"><span
|
||||
style="font-size: 11pt; background-color: rgb(255, 255, 255); font-family: 'Microsoft YaHei',serif; font-weight: normal; font-style: normal; text-decoration-line: none; color: rgb(0, 0, 0);"><br></span></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin: 0;padding: 0;position: relative;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0;padding-bottom:0; padding-top:0;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0 10px;padding-bottom:0; padding-top:0;">
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 11pt; line-height: 1.859; background-color: transparent; color: rgb(0, 0, 0);">
|
||||
<b
|
||||
style="color: rgb(0, 0, 0); font-size: 11pt; font-family: 'Microsoft YaHei',serif; background-color: transparent;"><span
|
||||
style="font-size: 16.001pt; color: rgb(38, 126, 240); font-family: 'Microsoft YaHei',serif; background-color: transparent;"><span
|
||||
style="color: rgb(38, 126, 240); font-size: 16.001pt; background-color: rgb(255, 255, 255); font-family: 'Microsoft YaHei',serif; font-style: normal; text-decoration-line: none;">画像报告</span></span></b>
|
||||
</div>
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 11pt; line-height: 1.859; background-color: transparent; color: rgb(0, 0, 0);">
|
||||
<b
|
||||
style="background-color: transparent; color: rgb(38, 126, 240); font-size: 16.001pt;"><span
|
||||
style="font-size: 16.001pt;"><a href="{{report_ticket}}" target="_blank"
|
||||
style="color: rgb(38, 126, 240); text-decoration-line: underline; outline-style: none; cursor: pointer; font-size: 16px; font-weight: 400;">{{report_ticket}}</a></span></b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin: 0;padding: 0;position: relative;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0;padding-bottom:0; padding-top:0;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0 10px;padding-bottom:0; padding-top:0;">
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 16.001pt; line-height: 1.859; background-color: transparent; color: rgb(38, 126, 240);">
|
||||
<b
|
||||
style="background-color: transparent; font-size: 16.001pt; margin-bottom:0; padding-bottom:0;"><span
|
||||
style="font-size: 16pt;">往期校验报告</span></b></div>
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 16.001pt; line-height: 1.859; background-color: transparent; color: rgb(38, 126, 240);">
|
||||
<b
|
||||
style="background-color: transparent; font-size: 16.001pt; margin-top:0; padding-top:0; margin-bottom:0; padding-bottom:0;">{{regression_testing}}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin: 0;padding: 0;position: relative;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0;padding-bottom:0; padding-top:0;">
|
||||
<div style="margin-bottom:0; padding-bottom:0; margin-top:0; padding-top:0;">
|
||||
<div style="margin: 0 10px;padding-bottom:0; padding-top:0;">
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 16.001pt; line-height: 1.859; background-color: transparent; color: rgb(38, 126, 240); margin-top:0; padding-top:0;">
|
||||
<b style="color: rgb(38, 126, 240); font-size: 16.001pt; margin-top:0; padding-top:0;"><span
|
||||
style="color: rgb(38, 126, 240); font-size: 16pt; font-weight: bold; background-color: rgb(255, 255, 255); font-family: 'Microsoft YaHei',serif; font-style: normal; text-decoration-line: none;">CMDP智能董办手册</span><br
|
||||
style="color: rgb(0, 0, 0); font-size: 16px; font-weight: 400; background-color: rgb(255, 255, 255);"></b>
|
||||
</div>
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 16.001pt; line-height: 1.859; background-color: transparent; color: rgb(38, 126, 240); margin-top:0; padding-top:0;">
|
||||
<b style="color: rgb(38, 126, 240); font-size: 16.001pt; margin-top:0; padding-top: 0;"><a
|
||||
href="https://a.cmdp.cn/v1/cloudfile/file/?ticket=LCXjyksIA" target="_blank"
|
||||
style="color: rgb(38, 126, 240); text-decoration-line: underline; outline-style: none; cursor: pointer; font-size: 16px; font-weight: 400; background-color: rgb(255, 255, 255);">https://a.cmdp.cn/v1/cloudfile/file/?ticket=LCXjyksIA</a><br
|
||||
style="color: rgb(0, 0, 0); font-size: 16px; font-weight: 400; background-color: rgb(255, 255, 255);"></b>
|
||||
</div>
|
||||
<!-- <div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 16.001pt; line-height: 1.859; background-color: transparent; color: rgb(38, 126, 240); margin-top:0; padding-top:0;">
|
||||
<br></div>
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 16pt; line-height: 1.859; background-color: transparent; color: rgb(38, 126, 240); margin-top:0; padding-top:0;">
|
||||
<span
|
||||
style="font-family: 'Microsoft YaHei',serif; font-size: 16pt; font-weight: bold; font-style: normal; text-decoration-line: none; color: rgb(38, 126, 240); background-color: rgb(255, 255, 255);">2023年年报小智智填-定期报告专版产品试用</span>
|
||||
</div>
|
||||
<div
|
||||
style="position: relative; margin-bottom:0; font-family: 'Microsoft YaHei',serif; font-size: 16pt; line-height: 1.859; background-color: transparent; color: rgb(38, 126, 240); margin-top:0; padding-top:0;">
|
||||
<span
|
||||
style="font-family: 'Microsoft YaHei',serif; font-size: 16pt; font-weight: bold; font-style: normal; text-decoration-line: none; color: rgb(38, 126, 240); background-color: rgb(255, 255, 255);"><img
|
||||
id="7100638799184082" src="data:application/octet-stream;base64,{{qrcode}}"
|
||||
style="vertical-align: bottom; width: 399px; height: 354px; max-width: initial;"
|
||||
width="399" height="354" alt="产品试用"></span></div> -->
|
||||
<div>{{picture}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
31
test.py
Normal file
31
test.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
|
||||
# 时间字符串转UTC时间的函数
|
||||
def convert_to_utc(time_str, time_format='%Y-%m-%d %H:%M:%S', source_timezone=None):
|
||||
# 解析时间字符串为datetime对象
|
||||
local_dt = datetime.datetime.strptime(time_str, time_format)
|
||||
|
||||
# 如果指定了源时区,则将本地时间转换为UTC时间
|
||||
if source_timezone:
|
||||
tz = pytz.timezone(source_timezone)
|
||||
local_dt = tz.localize(local_dt)
|
||||
utc_dt = local_dt.astimezone(pytz.UTC)
|
||||
else:
|
||||
# 如果没有指定源时区,则假设时间字符串已经是UTC时间
|
||||
utc_dt = pytz.UTC.localize(local_dt)
|
||||
|
||||
return utc_dt
|
||||
|
||||
|
||||
# 示例用法
|
||||
time_str = '2024-05-20 14:30:00'
|
||||
# 假设时间字符串是北京时间(UTC+8)
|
||||
utc_time = convert_to_utc(time_str, source_timezone='Asia/Shanghai')
|
||||
print(convert_to_utc(time_str).timestamp())
|
||||
print(f'UTC时间: {utc_time.strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
|
||||
# 如果需要获取UTC时间戳
|
||||
timestamp = utc_time.timestamp()
|
||||
print(f'UTC时间戳: {timestamp}')
|
284
xp/test.py
Normal file
284
xp/test.py
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user