Spaces:
Running
Running
rogerxavier
commited on
Commit
•
ee37dcf
1
Parent(s):
f7f23c0
Upload 20 files
Browse files- README.md +13 -0
- main.py +4 -0
- requirements.txt +8 -0
- server/__pycache__/api.cpython-38.pyc +0 -0
- server/__pycache__/dao_pool.cpython-38.pyc +0 -0
- server/__pycache__/getPoints.cpython-38.pyc +0 -0
- server/__pycache__/getPoolStatus.cpython-38.pyc +0 -0
- server/__pycache__/invite_code_api.cpython-38.pyc +0 -0
- server/__pycache__/utils.cpython-38.pyc +0 -0
- server/__pycache__/verification.cpython-38.pyc +0 -0
- server/account_manager.py +106 -0
- server/api.py +40 -0
- server/config.json +8 -0
- server/dao.py +168 -0
- server/dao_pool.py +59 -0
- server/hook.py +29 -0
- server/notify.py +64 -0
- server/pydanticModel.py +135 -0
- server/utils.py +10 -0
- test.py +76 -0
README.md
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: payHook
|
3 |
+
emoji: 🐨
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: pink
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 4.36.1
|
8 |
+
app_file: main.py
|
9 |
+
pinned: false
|
10 |
+
license: mit
|
11 |
+
---
|
12 |
+
|
13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
main.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import uvicorn
|
2 |
+
|
3 |
+
if __name__ == "__main__":
|
4 |
+
uvicorn.run("server.api:app", host="0.0.0.0", port=7860, reload=True)
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
requests==2.31.0
|
2 |
+
aiohttp==3.7.3
|
3 |
+
fastapi==0.112.0
|
4 |
+
urllib3==2.0.7
|
5 |
+
tablestore==6.0.1
|
6 |
+
pydantic==2.9.1 #用于自定义接口模型
|
7 |
+
uvicorn
|
8 |
+
mysql-connector-python
|
server/__pycache__/api.cpython-38.pyc
ADDED
Binary file (860 Bytes). View file
|
|
server/__pycache__/dao_pool.cpython-38.pyc
ADDED
Binary file (1.73 kB). View file
|
|
server/__pycache__/getPoints.cpython-38.pyc
ADDED
Binary file (982 Bytes). View file
|
|
server/__pycache__/getPoolStatus.cpython-38.pyc
ADDED
Binary file (474 Bytes). View file
|
|
server/__pycache__/invite_code_api.cpython-38.pyc
ADDED
Binary file (1.26 kB). View file
|
|
server/__pycache__/utils.cpython-38.pyc
ADDED
Binary file (919 Bytes). View file
|
|
server/__pycache__/verification.cpython-38.pyc
ADDED
Binary file (2.33 kB). View file
|
|
server/account_manager.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
|
3 |
+
from fastapi import HTTPException,APIRouter
|
4 |
+
from .pydanticModel import *
|
5 |
+
from .dao import tableStore
|
6 |
+
|
7 |
+
router = APIRouter()
|
8 |
+
|
9 |
+
# 定义路由和函数
|
10 |
+
@router.post("/login")
|
11 |
+
@router.get("/login")
|
12 |
+
async def login(login: signIn):
|
13 |
+
# ->"user info or throw error"
|
14 |
+
# 检查email和password是否匹配
|
15 |
+
table_store = tableStore(ots_client=router.ots_client,table_name=router.table_name)
|
16 |
+
try:
|
17 |
+
login_result = table_store.login(login.email, login.password)
|
18 |
+
if login_result['ec']==200:
|
19 |
+
return login_result
|
20 |
+
else:
|
21 |
+
raise HTTPException(status_code=400, detail="login failed in table store")
|
22 |
+
except Exception as e:
|
23 |
+
print(e)
|
24 |
+
raise HTTPException(status_code=400, detail="sign in failed")
|
25 |
+
|
26 |
+
@router.get("/signUp")
|
27 |
+
@router.post("/signUp")
|
28 |
+
async def sign_up(signUp: signUp):
|
29 |
+
# ->"user info or throw error"
|
30 |
+
table_store = tableStore(ots_client=router.ots_client,table_name=router.table_name)
|
31 |
+
try:
|
32 |
+
signUp_result = table_store.userSignUp(signUp.email,signUp.password)
|
33 |
+
if signUp_result['ec']==200:
|
34 |
+
return signUp_result
|
35 |
+
else:
|
36 |
+
raise HTTPException(status_code=400, detail="sign up failed in table store")
|
37 |
+
except Exception as e:
|
38 |
+
raise HTTPException(status_code=400, detail="sign up failed")
|
39 |
+
|
40 |
+
|
41 |
+
|
42 |
+
|
43 |
+
|
44 |
+
@router.get("/purchase")
|
45 |
+
@router.post("/purchase")
|
46 |
+
async def purchase(plan: userPlan):
|
47 |
+
#->bool or raise error
|
48 |
+
# 提取plan价格和时间
|
49 |
+
price = plan.price
|
50 |
+
time_to_add = plan.duration_seconds
|
51 |
+
email = plan.email
|
52 |
+
password = plan.password
|
53 |
+
table_store = tableStore(ots_client=router.ots_client, table_name=router.table_name)
|
54 |
+
# 读取存储数据,如果存的expiredAt>now,那么从expiredAt加时间
|
55 |
+
# 如果存的<now ,那么从now加时间 (总之是取两者较大+time_to_add)
|
56 |
+
|
57 |
+
table_store.getUserInfo(email=email)
|
58 |
+
cur_balance = table_store.balance
|
59 |
+
cur_expired_at = table_store.expired_at
|
60 |
+
cur_time = int(time.time())
|
61 |
+
user_password_stored = table_store.stored_password
|
62 |
+
|
63 |
+
|
64 |
+
if cur_balance -price <0:
|
65 |
+
raise HTTPException(status_code=400, detail="balance not enough")
|
66 |
+
elif password!=user_password_stored:
|
67 |
+
raise HTTPException(status_code=400, detail="auth not pass in purchase")
|
68 |
+
else:
|
69 |
+
expired_new = max(cur_expired_at, cur_time) + time_to_add
|
70 |
+
balance_new = cur_balance -price
|
71 |
+
# 更新余额和时间
|
72 |
+
update_balance_result = table_store.updateColumnByPrimaryKey(
|
73 |
+
key=router.key,
|
74 |
+
key_value=email,
|
75 |
+
update_column='balance',
|
76 |
+
update_column_value=balance_new
|
77 |
+
)
|
78 |
+
if not update_balance_result:
|
79 |
+
raise HTTPException(status_code=400, detail="updateBalance 结果失败")
|
80 |
+
update_expired_result = table_store.updateColumnByPrimaryKey(
|
81 |
+
key=router.key,
|
82 |
+
key_value=email,
|
83 |
+
update_column='expiredAt',
|
84 |
+
update_column_value=expired_new
|
85 |
+
)
|
86 |
+
if not update_expired_result:
|
87 |
+
raise HTTPException(status_code=400, detail="update_expired_result 结果失败")
|
88 |
+
return True
|
89 |
+
|
90 |
+
|
91 |
+
|
92 |
+
|
93 |
+
|
94 |
+
|
95 |
+
|
96 |
+
|
97 |
+
|
98 |
+
|
99 |
+
|
100 |
+
|
101 |
+
|
102 |
+
|
103 |
+
|
104 |
+
|
105 |
+
|
106 |
+
|
server/api.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from .notify import router as NotifyRouter
|
3 |
+
from .utils import config
|
4 |
+
from tablestore import OTSClient
|
5 |
+
from .account_manager import router as accountManagerRouter
|
6 |
+
access_key_id='LTAI5tNwpEHCMTupADmx6xA4'
|
7 |
+
access_key_secret="vX1q5Tvj3LSsIMUXN6SPbMUBCET3qG"
|
8 |
+
end_point='https://v2b-user-table.ap-southeast-1.ots.aliyuncs.com'
|
9 |
+
instance_name='v2b-user-table'
|
10 |
+
table_name = 'user'
|
11 |
+
primary_key = 'email'
|
12 |
+
ots_client = OTSClient(
|
13 |
+
end_point=end_point,
|
14 |
+
access_key_id=access_key_id,
|
15 |
+
access_key_secret=access_key_secret,
|
16 |
+
instance_name=instance_name
|
17 |
+
)
|
18 |
+
|
19 |
+
accountManagerRouter.key = primary_key
|
20 |
+
accountManagerRouter.ots_client = ots_client
|
21 |
+
accountManagerRouter.table_name = table_name
|
22 |
+
|
23 |
+
#和hook.pixiv.digital/uu兼容 故分开
|
24 |
+
NotifyRouter.key = primary_key
|
25 |
+
NotifyRouter.ots_client = ots_client
|
26 |
+
NotifyRouter.table_name = table_name
|
27 |
+
|
28 |
+
|
29 |
+
|
30 |
+
app = FastAPI()
|
31 |
+
@app.get("/config.json", tags=["Root"])
|
32 |
+
async def read_root() -> dict:
|
33 |
+
return config
|
34 |
+
|
35 |
+
# 全局路由
|
36 |
+
app.include_router(accountManagerRouter,prefix='/accountManager')
|
37 |
+
app.include_router(NotifyRouter, prefix=config['notify']) # https://hook.pixiv.digital/uu接受回调
|
38 |
+
if __name__ == '__main__':
|
39 |
+
print(config['notify'])
|
40 |
+
|
server/config.json
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
{
|
3 |
+
"notify": "/uu",
|
4 |
+
"databaseName":"roger",
|
5 |
+
"databaseUser": "roger",
|
6 |
+
"password": "tWTcHxwKDtBGm6eJ",
|
7 |
+
"host": "146.190.67.151"
|
8 |
+
}
|
server/dao.py
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
from tablestore import *
|
3 |
+
|
4 |
+
# 定义一个自定义错误类
|
5 |
+
class customError(Exception):
|
6 |
+
def __init__(self, message,original_exception=None):
|
7 |
+
super().__init__(message)
|
8 |
+
self.message = message
|
9 |
+
self.original_expection = original_exception
|
10 |
+
print("original exception:",original_exception)
|
11 |
+
|
12 |
+
class tableStore:
|
13 |
+
def __init__(self,ots_client,table_name):
|
14 |
+
self.ots_client = ots_client
|
15 |
+
self.table_name = table_name
|
16 |
+
self.email = None
|
17 |
+
self.stored_password = None
|
18 |
+
self.is_vip = None
|
19 |
+
self.expired_at = None
|
20 |
+
self.balance = None
|
21 |
+
|
22 |
+
def getUserInfo(self,email)->"void or throw error":
|
23 |
+
#通过email主键查询用户信息
|
24 |
+
primary_key = [
|
25 |
+
['email', email],
|
26 |
+
]
|
27 |
+
columns_to_get = ['isVip', 'expiredAt', 'password','balance']
|
28 |
+
try:
|
29 |
+
consumed, return_row, next_token = self.ots_client.get_row(
|
30 |
+
table_name=self.table_name,
|
31 |
+
primary_key=primary_key,
|
32 |
+
columns_to_get=columns_to_get
|
33 |
+
)
|
34 |
+
if return_row is None:
|
35 |
+
raise customError('email not exist')
|
36 |
+
else:
|
37 |
+
# 创建一个字典来存储键值对
|
38 |
+
attributes = {}
|
39 |
+
for key, value, _ in return_row.attribute_columns:
|
40 |
+
attributes[key] = value
|
41 |
+
|
42 |
+
self.email = email
|
43 |
+
self.expired_at = attributes['expiredAt']
|
44 |
+
self.is_vip = attributes['isVip']
|
45 |
+
self.stored_password = attributes['password']
|
46 |
+
self.balance = attributes['balance']
|
47 |
+
|
48 |
+
except Exception as e:
|
49 |
+
raise customError("请求getUserInfo失败",e)
|
50 |
+
def checkInit(self)->"bool or throw error":
|
51 |
+
# 使用 all() 函数检查所有属性是否都存在且不为 None
|
52 |
+
return all([self.email is not None, self.stored_password is not None, self.is_vip is not None,
|
53 |
+
self.expired_at is not None])
|
54 |
+
|
55 |
+
def login(self, email, password)-> "user info or throw error":
|
56 |
+
self.getUserInfo(email) #检测登录前先获取用户信息
|
57 |
+
if self.checkInit():
|
58 |
+
#都存在说明正常初始化
|
59 |
+
if self.email==email and self.stored_password==password:
|
60 |
+
return {"ec":200,"expiredAt":self.expired_at
|
61 |
+
,"balance":self.balance,"email":email,"password":password}
|
62 |
+
else:
|
63 |
+
raise customError("login验证失败-账户不匹配")
|
64 |
+
else:
|
65 |
+
raise customError("login调用时发现用户信息没有正常初始化")
|
66 |
+
def checkVipStatus(self)->"bool or throw error":
|
67 |
+
current_time = int(time.time())
|
68 |
+
if self.checkInit():
|
69 |
+
if self.expired_at > current_time:
|
70 |
+
#只看截止时间
|
71 |
+
return True
|
72 |
+
else:
|
73 |
+
return False
|
74 |
+
else:
|
75 |
+
raise customError("checkVipStatus调用时发现用户信息没有正常初始化")
|
76 |
+
|
77 |
+
def userSignUp(self,email,password)->"user info or throw error":
|
78 |
+
#如果收到参数不够,那么务必设置默认值 expiredAt->0 ,isVip->false
|
79 |
+
primary_key = [
|
80 |
+
['email', email],
|
81 |
+
]
|
82 |
+
attribute_columns = [
|
83 |
+
['isVip', False], # 注册用户不为vip
|
84 |
+
['password', password],
|
85 |
+
['expiredAt', int(time.time())], # 第一次创建过期时间为当前时间
|
86 |
+
['balance', int(0)], # 第一次创建balance为0
|
87 |
+
]
|
88 |
+
try:
|
89 |
+
self.ots_client.put_row(
|
90 |
+
table_name=self.table_name,
|
91 |
+
row=Row(
|
92 |
+
primary_key=primary_key,
|
93 |
+
attribute_columns=attribute_columns
|
94 |
+
),
|
95 |
+
condition=Condition(RowExistenceExpectation.EXPECT_NOT_EXIST) # 这种只要存在就不执行(多次注册只有第一次生效)
|
96 |
+
)
|
97 |
+
return {"ec":200,"expiredAt":0
|
98 |
+
,"balance":0,"email":email,"password":password}
|
99 |
+
except Exception as e:
|
100 |
+
raise customError("userSignUp 失败",e)
|
101 |
+
|
102 |
+
def updateColumnByPrimaryKey(self,key:str,key_value:'dynamic',
|
103 |
+
update_column:str,update_column_value:'dynamic')\
|
104 |
+
-> "bool or throw error":
|
105 |
+
#比如支付回调的时候没有密码,故而只根据主键email更新时间
|
106 |
+
primary_key = [
|
107 |
+
(key, key_value),
|
108 |
+
]
|
109 |
+
update_of_attribute_columns = {
|
110 |
+
'PUT': [(update_column, update_column_value)]
|
111 |
+
}
|
112 |
+
updateRow = Row(
|
113 |
+
primary_key=primary_key,
|
114 |
+
attribute_columns=update_of_attribute_columns
|
115 |
+
)
|
116 |
+
try:
|
117 |
+
table_meta = self.ots_client.describe_table(table_name=self.table_name)
|
118 |
+
defined_columns = table_meta.table_meta.defined_columns # 定义的非主键列-不受意外列添加影响
|
119 |
+
isColumnExist = any(column_name == update_column for column_name, _ in defined_columns)
|
120 |
+
condition = Condition(RowExistenceExpectation.EXPECT_EXIST)
|
121 |
+
|
122 |
+
if isColumnExist:
|
123 |
+
# 执行UpdateRow操作
|
124 |
+
self.ots_client.update_row(
|
125 |
+
table_name=self.table_name,
|
126 |
+
row=updateRow,
|
127 |
+
condition=condition
|
128 |
+
)
|
129 |
+
return True
|
130 |
+
else:
|
131 |
+
raise customError("更新列名不存在")
|
132 |
+
except Exception as e:
|
133 |
+
raise customError('更新操作发生问题',e)
|
134 |
+
|
135 |
+
|
136 |
+
|
137 |
+
|
138 |
+
if __name__ == '__main__':
|
139 |
+
access_key_id = 'LTAI5tNwpEHCMTupADmx6xA4'
|
140 |
+
access_key_secret = "vX1q5Tvj3LSsIMUXN6SPbMUBCET3qG"
|
141 |
+
end_point = 'https://v2b-user-table.ap-southeast-1.ots.aliyuncs.com'
|
142 |
+
instance_name = 'v2b-user-table'
|
143 |
+
table_name = 'user'
|
144 |
+
ots_client = OTSClient(
|
145 |
+
end_point=end_point,
|
146 |
+
access_key_id=access_key_id,
|
147 |
+
access_key_secret=access_key_secret,
|
148 |
+
instance_name=instance_name
|
149 |
+
)
|
150 |
+
tablestore = tableStore(ots_client=ots_client,table_name=table_name)
|
151 |
+
tablestore.updateColumnByPrimaryKey(key='email',key_value='user@example.com',
|
152 |
+
update_column='balance',update_column_value=1728877808)
|
153 |
+
|
154 |
+
|
155 |
+
|
156 |
+
|
157 |
+
|
158 |
+
|
159 |
+
|
160 |
+
|
161 |
+
|
162 |
+
|
163 |
+
|
164 |
+
|
165 |
+
|
166 |
+
|
167 |
+
|
168 |
+
|
server/dao_pool.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import mysql.connector
|
2 |
+
from mysql.connector import pooling
|
3 |
+
from .utils import config
|
4 |
+
class Database:
|
5 |
+
__connection_pool = None
|
6 |
+
@classmethod
|
7 |
+
def init(cls):
|
8 |
+
try:
|
9 |
+
cls.__connection_pool = pooling.MySQLConnectionPool(
|
10 |
+
pool_name="v2boardPool",
|
11 |
+
pool_size=5,
|
12 |
+
host=config["host"],
|
13 |
+
database=config["databaseName"],
|
14 |
+
user=config["databaseUser"],
|
15 |
+
password=config["password"]
|
16 |
+
)
|
17 |
+
except mysql.connector.Error as e:
|
18 |
+
print("Error initializing connection pool: {}".format(str(e)))
|
19 |
+
|
20 |
+
@classmethod
|
21 |
+
def get_connection(cls):
|
22 |
+
if cls.__connection_pool is None:
|
23 |
+
cls.init()
|
24 |
+
conn = cls.__connection_pool.get_connection()
|
25 |
+
return conn
|
26 |
+
|
27 |
+
@classmethod
|
28 |
+
def release_connection(cls, connection):
|
29 |
+
connection.close()
|
30 |
+
|
31 |
+
@classmethod
|
32 |
+
def close_all_connections(cls):
|
33 |
+
cls.__connection_pool.close()
|
34 |
+
|
35 |
+
@classmethod
|
36 |
+
def status(cls):
|
37 |
+
# 获取使用了的连接状态
|
38 |
+
if cls.__connection_pool is None:
|
39 |
+
cls.init()
|
40 |
+
return cls.__connection_pool.get_pool_size()
|
41 |
+
|
42 |
+
if __name__ == '__main__':
|
43 |
+
email = 'hanryr.dios482992030@gmail.com'
|
44 |
+
with Database.get_connection() as conn:
|
45 |
+
with conn.cursor() as cur:
|
46 |
+
print("链接数据库成功")
|
47 |
+
query = "SELECT balance FROM `v2_user` WHERE email = %(email)s"
|
48 |
+
params = {'email': email}
|
49 |
+
cur.execute(query, params)
|
50 |
+
result = cur.fetchone()
|
51 |
+
|
52 |
+
if result is not None:
|
53 |
+
# 如果email已经存在于数据库中,那么从数据库中获取balance
|
54 |
+
balance = result[0]
|
55 |
+
print(balance)
|
56 |
+
else:
|
57 |
+
# 如果email不存在于数据库中,提示用户未注册(没必要,因为没法通知到app),所以不做处理
|
58 |
+
pass
|
59 |
+
print("数据库操作完成")
|
server/hook.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#notify通知到后自动写入数据库的hook操作,只添加余额就行,连月都不用管
|
2 |
+
from .dao_pool import Database
|
3 |
+
def updateBalance(email,amount):
|
4 |
+
##通过传入用户email和回调中的金额修改balance
|
5 |
+
with Database.get_connection() as conn:
|
6 |
+
with conn.cursor() as cur:
|
7 |
+
print("链接数据库成功")
|
8 |
+
query = "SELECT balance FROM `v2_user` WHERE email = %(email)s"
|
9 |
+
params = {'email': email}
|
10 |
+
cur.execute(query, params)
|
11 |
+
result = cur.fetchone()
|
12 |
+
|
13 |
+
if result is not None:
|
14 |
+
# 如果email已经存在于数据库中,那么从数据库中获取balance,让balance加上新充值的金额就行
|
15 |
+
balance = result[0]
|
16 |
+
balance +=float(amount)*100
|
17 |
+
print(balance)
|
18 |
+
query = "UPDATE `v2_user` SET balance = %(balance)s WHERE email = %(email)s"
|
19 |
+
params = {'balance': str(balance), 'email': email}
|
20 |
+
cur.execute(query, params)
|
21 |
+
conn.commit()
|
22 |
+
else:
|
23 |
+
# 如果email不存在于数据库中,提示用户未注册(没必要,因为没法通知到app),所以不做处理
|
24 |
+
pass
|
25 |
+
print("数据库操作完成")
|
26 |
+
|
27 |
+
|
28 |
+
|
29 |
+
|
server/notify.py
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from fastapi import APIRouter, HTTPException
|
3 |
+
from fastapi.responses import JSONResponse
|
4 |
+
from .hook import *
|
5 |
+
from .pydanticModel import *
|
6 |
+
from .dao import tableStore
|
7 |
+
|
8 |
+
router = APIRouter()
|
9 |
+
#afdian的回调是post
|
10 |
+
@router.get('/',response_model=AfdianResp)
|
11 |
+
@router.post('/',response_model=AfdianResp)
|
12 |
+
async def update_balance(afdianHookjson: AfdianHookJson):
|
13 |
+
|
14 |
+
resp = {'ec': 200}
|
15 |
+
# 检查 ec 是否为 200
|
16 |
+
if afdianHookjson.ec != 200:
|
17 |
+
return {'ec': "afdian hook错误,ec 不是 200"}
|
18 |
+
|
19 |
+
# 提取订单详情和可选参数
|
20 |
+
order_details = afdianHookjson.data.order
|
21 |
+
custom_order_id = order_details.custom_order_id
|
22 |
+
total_amount = order_details.total_amount
|
23 |
+
|
24 |
+
table_store = tableStore(ots_client=router.ots_client, table_name=router.table_name)
|
25 |
+
|
26 |
+
if all([custom_order_id is not None, total_amount is not None]):
|
27 |
+
|
28 |
+
# 说明是余额类型
|
29 |
+
try:
|
30 |
+
# 先给mysql的数据库添加 - 这个毕竟久 稳定 但是不应该影响其他数据库添加(万一不用了)--------------------
|
31 |
+
updateBalance(email=custom_order_id, amount=total_amount)
|
32 |
+
# 先给mysql的数据库添加 - 这个毕竟久 稳定 但是不应该影响其他数据库添加(万一不用了)--------------------
|
33 |
+
except Exception as e:
|
34 |
+
print("updateBalance v2b digitalocean mysql failed",e)
|
35 |
+
|
36 |
+
try:
|
37 |
+
#应该改成从原来的余额基础上加total_amount的值
|
38 |
+
table_store.getUserInfo(email=custom_order_id)
|
39 |
+
cur_balance = table_store.balance
|
40 |
+
balance_new = cur_balance+ total_amount
|
41 |
+
# 更新余额列
|
42 |
+
update_balance_result = table_store.updateColumnByPrimaryKey(
|
43 |
+
key=router.key,
|
44 |
+
key_value=custom_order_id,
|
45 |
+
update_column='balance',
|
46 |
+
update_column_value=balance_new
|
47 |
+
)
|
48 |
+
if update_balance_result:
|
49 |
+
return resp #全部成功运行则返回爱发电要求的ec =200
|
50 |
+
else:
|
51 |
+
return {'ec': "updateBalance tablestore 结果失败"}
|
52 |
+
except Exception as e:
|
53 |
+
print(e)
|
54 |
+
return {'ec': "尝试 updateBalance tablestore 失败"}
|
55 |
+
else:
|
56 |
+
#这个直接返回200吧,反正测试接口的时候需要 ,平时也不用到
|
57 |
+
# return {'ec': "afdian hook custom_order_id 或者 total_amount 为 None"}
|
58 |
+
return resp
|
59 |
+
|
60 |
+
# test
|
61 |
+
# @router.get('/',response_model=AfdianResp)
|
62 |
+
# async def update_balance(afdianHookjson: AfdianHookJson):
|
63 |
+
# print("收到请求1")
|
64 |
+
# return {'ec':200}
|
server/pydanticModel.py
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel,field_validator,Field
|
2 |
+
from fastapi import Query
|
3 |
+
from typing import List, Optional, Any
|
4 |
+
|
5 |
+
|
6 |
+
# 定义fastapi请求参数模型
|
7 |
+
|
8 |
+
class signUp(BaseModel):
|
9 |
+
email: str
|
10 |
+
password: str
|
11 |
+
# 可选参数
|
12 |
+
expiredAt: int = Query(default=0, alias="expiredAt")
|
13 |
+
balance: int = Query(default=0, alias="balance")
|
14 |
+
ec: int = Query(default=200, alias="ec")
|
15 |
+
|
16 |
+
|
17 |
+
@field_validator('email')
|
18 |
+
#确保password不为''
|
19 |
+
def check_email(cls, v):
|
20 |
+
if v == '':
|
21 |
+
raise ValueError('email cannot be an empty string')
|
22 |
+
return v
|
23 |
+
|
24 |
+
@field_validator('password')
|
25 |
+
# 确保password不为''
|
26 |
+
def check_password(cls, v):
|
27 |
+
if v == '':
|
28 |
+
raise ValueError('password cannot be an empty string')
|
29 |
+
return v
|
30 |
+
|
31 |
+
|
32 |
+
class signIn(BaseModel):
|
33 |
+
email: str
|
34 |
+
password: str
|
35 |
+
# 可选参数
|
36 |
+
expiredAt: int = Query(default=0, alias="expiredAt")
|
37 |
+
balance: int = Query(default=0, alias="balance")
|
38 |
+
ec: int = Query(default=200, alias="ec")
|
39 |
+
|
40 |
+
|
41 |
+
@field_validator('email')
|
42 |
+
# 确保password不为''
|
43 |
+
def check_email(cls, v):
|
44 |
+
if v == '':
|
45 |
+
raise ValueError('email cannot be an empty string')
|
46 |
+
return v
|
47 |
+
|
48 |
+
@field_validator('password')
|
49 |
+
# 确保password不为''
|
50 |
+
def check_password(cls, v):
|
51 |
+
if v == '':
|
52 |
+
raise ValueError('password cannot be an empty string')
|
53 |
+
return v
|
54 |
+
|
55 |
+
|
56 |
+
class SKUDetail(BaseModel):
|
57 |
+
sku_id: str
|
58 |
+
price: str
|
59 |
+
count: int
|
60 |
+
name: str
|
61 |
+
album_id: Optional[str] = None
|
62 |
+
pic: Optional[str] = None
|
63 |
+
stock: Optional[str] = None
|
64 |
+
post_id: Optional[str] = None
|
65 |
+
|
66 |
+
|
67 |
+
class AfdianOrderDetail(BaseModel):
|
68 |
+
out_trade_no: str
|
69 |
+
user_id: str
|
70 |
+
plan_id: str
|
71 |
+
month: int
|
72 |
+
show_amount: str
|
73 |
+
status: int
|
74 |
+
remark: str
|
75 |
+
redeem_id: str
|
76 |
+
product_type: int
|
77 |
+
discount: str
|
78 |
+
sku_detail: List[SKUDetail] # 更新为 SKUDetail 列表
|
79 |
+
create_time: int
|
80 |
+
plan_title: str
|
81 |
+
user_private_id: str
|
82 |
+
address_person: str
|
83 |
+
address_phone: str
|
84 |
+
address_address: str
|
85 |
+
custom_order_id: Optional[str] = Field(default=None, alias="custom_order_id")
|
86 |
+
total_amount: Optional[int] = Field(default=None, alias="total_amount") #这里自动转int了
|
87 |
+
|
88 |
+
class AfdianOrderData(BaseModel):
|
89 |
+
type: str
|
90 |
+
order: AfdianOrderDetail
|
91 |
+
|
92 |
+
|
93 |
+
class AfdianHookJson(BaseModel):
|
94 |
+
ec: int
|
95 |
+
em: str
|
96 |
+
data: AfdianOrderData
|
97 |
+
|
98 |
+
|
99 |
+
class AfdianResp(BaseModel):
|
100 |
+
ec: Any
|
101 |
+
|
102 |
+
|
103 |
+
|
104 |
+
#根据plan中的价格和时间相应减少balance和增加expiredAt
|
105 |
+
class userPlan(BaseModel):
|
106 |
+
price: int
|
107 |
+
duration_seconds: int
|
108 |
+
email: str #订阅plan的用户
|
109 |
+
password: str #请求purchase接口用户的password,保证只有用户本身可以请求这个接口防止滥用
|
110 |
+
|
111 |
+
@field_validator('price')
|
112 |
+
# 确保password不为''
|
113 |
+
def check_price(cls, v):
|
114 |
+
if v < 0:
|
115 |
+
raise ValueError('price can not < 0')
|
116 |
+
return v
|
117 |
+
|
118 |
+
@field_validator('duration_seconds')
|
119 |
+
# 确保password不为''
|
120 |
+
def check_duration_seconds(cls, v):
|
121 |
+
if v < 0:
|
122 |
+
raise ValueError('duration_seconds can not < 0')
|
123 |
+
return v
|
124 |
+
|
125 |
+
@field_validator('email')
|
126 |
+
# 确保password不为''
|
127 |
+
def check_email(cls, v):
|
128 |
+
if v == '':
|
129 |
+
raise ValueError('email cannot be an empty string')
|
130 |
+
return v
|
131 |
+
|
132 |
+
|
133 |
+
|
134 |
+
|
135 |
+
|
server/utils.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
# 获取当前文件所在的目录
|
4 |
+
current_dir = os.path.dirname(os.path.realpath(__file__))
|
5 |
+
# 读取配置文件
|
6 |
+
with open(os.path.join(current_dir, 'config.json'), 'r') as f:
|
7 |
+
config = json.load(f)
|
8 |
+
|
9 |
+
if __name__ == '__main__':
|
10 |
+
print(config['notify'])
|
test.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
# port = 7860
|
3 |
+
port= 3000
|
4 |
+
|
5 |
+
# url = "http://localhost:{p}/accountManager/login".format(p=port)
|
6 |
+
url = "http://localhost:{p}/accountManager/signUp".format(p=port)
|
7 |
+
data = {
|
8 |
+
"email": '1234567891011@qq.com',
|
9 |
+
"password": '1234567891011@qq.com'
|
10 |
+
}
|
11 |
+
response = requests.get(url, json=data)
|
12 |
+
print(response.status_code)
|
13 |
+
print(response.json())
|
14 |
+
|
15 |
+
|
16 |
+
# #hook模拟
|
17 |
+
# # port = 7860
|
18 |
+
# # url = "http://localhost:{p}/uu".format(p=port)
|
19 |
+
# url = 'https://hook.pixiv.digital/uu'
|
20 |
+
# data = {
|
21 |
+
# "ec": 200,
|
22 |
+
# "em": "ok",
|
23 |
+
# "data": {
|
24 |
+
# "type": "order",
|
25 |
+
# "order": {
|
26 |
+
# "out_trade_no": "20241014133823102101567665",
|
27 |
+
# "user_id": "f05ca070734c11efa78052540025c377",
|
28 |
+
# "plan_id": "ef3c43c6daa411ee965e5254001e7c00",
|
29 |
+
# "month": 1,
|
30 |
+
# "total_amount": "10.00",
|
31 |
+
# "show_amount": "10.00",
|
32 |
+
# "status": 2,
|
33 |
+
# "remark": "",
|
34 |
+
# "redeem_id": "",
|
35 |
+
# "product_type": 1,
|
36 |
+
# "discount": "0.00",
|
37 |
+
# "sku_detail": [
|
38 |
+
# {
|
39 |
+
# "sku_id": "ef440afcdaa411ee9e165254001e7c00",
|
40 |
+
# "price": "1.00",
|
41 |
+
# "count": 10,
|
42 |
+
# "name": "\u4f59\u989d\u6570\u91cf",
|
43 |
+
# "album_id": "",
|
44 |
+
# "pic": "",
|
45 |
+
# "stock": "",
|
46 |
+
# "post_id": ""
|
47 |
+
# }
|
48 |
+
# ],
|
49 |
+
# "create_time": 1728884303,
|
50 |
+
# "plan_title": "\u4f59\u989d",
|
51 |
+
# "user_private_id": "f14e880d78ac9d5c623b677c6e0c7122f1158bf0",
|
52 |
+
# "address_person": "",
|
53 |
+
# "address_phone": "",
|
54 |
+
# "address_address": "",
|
55 |
+
# "custom_order_id": "12345678910@qq.com"
|
56 |
+
# }
|
57 |
+
# }
|
58 |
+
# }
|
59 |
+
# response = requests.get(url, json=data)
|
60 |
+
# print(response.status_code)
|
61 |
+
# print(response.text)
|
62 |
+
|
63 |
+
|
64 |
+
|
65 |
+
# # purchase 模拟
|
66 |
+
# url = "http://localhost:{p}/accountManager/purchase".format(p=port)
|
67 |
+
# data = {
|
68 |
+
# "price":10,
|
69 |
+
# "duration_seconds":2592000,
|
70 |
+
# "email":'1320890187@qq.com',
|
71 |
+
# "password":"1320890187@qq.com"
|
72 |
+
# }
|
73 |
+
#
|
74 |
+
# response = requests.get(url, json=data)
|
75 |
+
# print(response.status_code)
|
76 |
+
# print(response.json())
|