Merge branch 'main' of git.opencomputing.cn:yumoqing/kboss
This commit is contained in:
commit
f9609403b5
@ -1,3 +1,19 @@
|
|||||||
|
async def get_password_tenant_orgid(sor, ns):
|
||||||
|
domain_name = ns.get('domain_name')
|
||||||
|
if (not domain_name) and ns.get('url_link'):
|
||||||
|
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
||||||
|
if domain_name:
|
||||||
|
domain_name = domain_name.strip().replace('https://', '').replace('http://', '').replace('/', '')
|
||||||
|
if 'localhost' in domain_name:
|
||||||
|
domain_name = 'dev.opencomputing.cn'
|
||||||
|
reseller = await sor.R('reseller', {'domain_name': domain_name, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return reseller[0]['orgid']
|
||||||
|
org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(org) >= 1:
|
||||||
|
return org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def forgotPassword(ns):
|
async def forgotPassword(ns):
|
||||||
"""
|
"""
|
||||||
忘记密码:校验短信验证码后重置密码。
|
忘记密码:校验短信验证码后重置密码。
|
||||||
@ -25,6 +41,10 @@ async def forgotPassword(ns):
|
|||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
try:
|
try:
|
||||||
|
tenant_orgid = await get_password_tenant_orgid(sor, ns)
|
||||||
|
if not tenant_orgid:
|
||||||
|
return {'status': False, 'msg': '未找到当前域名所属租户'}
|
||||||
|
|
||||||
code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')})
|
code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')})
|
||||||
if code:
|
if code:
|
||||||
create_at = code[0]['create_at']
|
create_at = code[0]['create_at']
|
||||||
@ -37,17 +57,21 @@ async def forgotPassword(ns):
|
|||||||
|
|
||||||
user = None
|
user = None
|
||||||
if ns.get('id'):
|
if ns.get('id'):
|
||||||
users = await sor.R('users', {'id': ns.get('id'), 'del_flg': '0'})
|
user_sql = """select * from users where id = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('id'), tenant_orgid)
|
||||||
|
users = await sor.sqlExe(user_sql, {})
|
||||||
if users:
|
if users:
|
||||||
user = users[0]
|
user = users[0]
|
||||||
elif ns.get('mobile'):
|
elif ns.get('mobile'):
|
||||||
users = await sor.R('users', {'mobile': ns.get('mobile'), 'del_flg': '0'})
|
user_sql = """select * from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('mobile'), tenant_orgid)
|
||||||
|
users = await sor.sqlExe(user_sql, {})
|
||||||
if users:
|
if users:
|
||||||
user = users[0]
|
user = users[0]
|
||||||
elif ns.get('username'):
|
elif ns.get('username'):
|
||||||
users = await sor.R('users', {'username': ns.get('username'), 'del_flg': '0'})
|
user_sql = """select * from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), tenant_orgid)
|
||||||
|
users = await sor.sqlExe(user_sql, {})
|
||||||
if not users:
|
if not users:
|
||||||
users = await sor.R('users', {'mobile': ns.get('username'), 'del_flg': '0'})
|
user_sql = """select * from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), tenant_orgid)
|
||||||
|
users = await sor.sqlExe(user_sql, {})
|
||||||
if users:
|
if users:
|
||||||
user = users[0]
|
user = users[0]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -108,6 +108,22 @@ async def sync_cn_ai_user(userid=None, orgid=None, username=None, name=None, ema
|
|||||||
'msg': f"sync_cn_ai_user{userid}同步用户失败: {e}"
|
'msg': f"sync_cn_ai_user{userid}同步用户失败: {e}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def get_register_tenant_orgid(sor, ns):
|
||||||
|
domain_name = ns.get('domain_name')
|
||||||
|
if (not domain_name) and ns.get('url_link'):
|
||||||
|
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
||||||
|
if domain_name:
|
||||||
|
domain_name = domain_name.strip().replace('https://', '').replace('http://', '').replace('/', '')
|
||||||
|
if 'localhost' in domain_name:
|
||||||
|
domain_name = 'dev.opencomputing.cn'
|
||||||
|
reseller = await sor.R('reseller', {'domain_name': domain_name, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return reseller[0]['orgid']
|
||||||
|
org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(org) >= 1:
|
||||||
|
return org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def registerUser(ns):
|
async def registerUser(ns):
|
||||||
"""
|
"""
|
||||||
用户注册
|
用户注册
|
||||||
@ -116,25 +132,30 @@ async def registerUser(ns):
|
|||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
if ns:
|
if ns:
|
||||||
|
tenant_orgid = await get_register_tenant_orgid(sor, ns)
|
||||||
|
if not tenant_orgid:
|
||||||
|
return {'status': False, 'msg': '未找到当前域名所属租户'}
|
||||||
|
|
||||||
|
# 用户名没有 则用手机号作为用户名
|
||||||
|
if not ns.get('username'):
|
||||||
|
ns['username'] = ns['mobile']
|
||||||
|
|
||||||
if ns.get('username'):
|
if ns.get('username'):
|
||||||
userns = {'username': ns['username']}
|
user_sql = """select id from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns['username'], tenant_orgid)
|
||||||
isuser = await sor.R('users', userns)
|
isuser = await sor.sqlExe(user_sql, {})
|
||||||
if len(isuser) >= 1:
|
if len(isuser) >= 1:
|
||||||
return {'status': False, 'msg': '用户名已注册'}
|
return {'status': False, 'msg': '用户名已注册'}
|
||||||
|
|
||||||
if ns.get('email'):
|
if ns.get('email'):
|
||||||
useremai = {'email': ns['email']}
|
email_sql = """select id from users where email = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns['email'], tenant_orgid)
|
||||||
isuser = await sor.R('users', useremai)
|
isuser = await sor.sqlExe(email_sql, {})
|
||||||
if len(isuser) >= 1:
|
if len(isuser) >= 1:
|
||||||
return {'status': False, 'msg': '该邮箱已注册'}
|
return {'status': False, 'msg': '该邮箱已注册'}
|
||||||
|
|
||||||
usermobile = {'mobile':ns['mobile']}
|
mobile_sql = """select id from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns['mobile'], tenant_orgid)
|
||||||
isuser = await sor.R('users', usermobile)
|
isuser = await sor.sqlExe(mobile_sql, {})
|
||||||
if len(isuser) >= 1:
|
if len(isuser) >= 1:
|
||||||
return {'status': False, 'msg': '该手机号已注册'}
|
return {'status': False, 'msg': '该手机号已注册'}
|
||||||
# 用户名没有 则用手机号作为用户名
|
|
||||||
if not ns.get('username'):
|
|
||||||
ns['username'] = ns['mobile']
|
|
||||||
|
|
||||||
# if ns.get('password'):
|
# if ns.get('password'):
|
||||||
# # 至少8位,包含大小写字母、特殊字符、数字
|
# # 至少8位,包含大小写字母、特殊字符、数字
|
||||||
@ -188,7 +209,9 @@ async def registerUser(ns):
|
|||||||
userid = ns['id']
|
userid = ns['id']
|
||||||
ns['orgid'] = orgid
|
ns['orgid'] = orgid
|
||||||
ns['password'] = password_encode(ns['password'])
|
ns['password'] = password_encode(ns['password'])
|
||||||
|
ns['tenant_orgid'] = tenant_orgid
|
||||||
await sor.C('users', ns)
|
await sor.C('users', ns)
|
||||||
|
ns.pop('tenant_orgid', None)
|
||||||
listrole = ['管理员', '客户']
|
listrole = ['管理员', '客户']
|
||||||
for i in listrole:
|
for i in listrole:
|
||||||
role = await sor.R('role', {'role': i, 'org_type': ns['org_type']})
|
role = await sor.R('role', {'role': i, 'org_type': ns['org_type']})
|
||||||
@ -274,7 +297,9 @@ async def registerUser(ns):
|
|||||||
ns['orgid'] = org_id
|
ns['orgid'] = org_id
|
||||||
|
|
||||||
# 新增用户信息
|
# 新增用户信息
|
||||||
|
ns['tenant_orgid'] = tenant_orgid
|
||||||
await sor.C('users', ns)
|
await sor.C('users', ns)
|
||||||
|
ns.pop('tenant_orgid', None)
|
||||||
|
|
||||||
# 新增用户角色信息
|
# 新增用户角色信息
|
||||||
listrole = ['管理员', '客户']
|
listrole = ['管理员', '客户']
|
||||||
|
|||||||
@ -1,66 +1,29 @@
|
|||||||
async def upload_file(ns={}):
|
async def upload_file(ns={}):
|
||||||
import os
|
debug('ns: %s' % ns)
|
||||||
import base64
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
file_name = ns.get('file_name')
|
|
||||||
file_content = ns.get('file_content') or ns.get('file_base64')
|
|
||||||
storage_type = ns.get('storage_type', 'local')
|
|
||||||
|
|
||||||
|
file_name = ns.get('file_data')
|
||||||
if not file_name:
|
if not file_name:
|
||||||
return {
|
return {
|
||||||
'status': False,
|
'status': False,
|
||||||
'msg': '请传递file_name'
|
'msg': 'file_data不能为空'
|
||||||
}
|
}
|
||||||
if not file_content:
|
domain_name = ns.get('domain_name')
|
||||||
|
if not domain_name:
|
||||||
return {
|
return {
|
||||||
'status': False,
|
'status': False,
|
||||||
'msg': '请传递file_content'
|
'msg': 'domain_name不能为空'
|
||||||
}
|
|
||||||
if storage_type != 'local':
|
|
||||||
return {
|
|
||||||
'status': False,
|
|
||||||
'msg': '暂不支持该存储类型'
|
|
||||||
}
|
|
||||||
|
|
||||||
safe_file_name = os.path.basename(file_name).replace('\\', '').replace('/', '')
|
|
||||||
if not safe_file_name:
|
|
||||||
return {
|
|
||||||
'status': False,
|
|
||||||
'msg': 'file_name错误'
|
|
||||||
}
|
}
|
||||||
|
if 'localhost' in domain_name:
|
||||||
|
domain_name = 'dev.opencomputing.cn'
|
||||||
|
|
||||||
|
save_url = 'https://%s/idfile?path=%s' % (domain_name, file_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if ',' in file_content and file_content.split(',', 1)[0].startswith('data:'):
|
|
||||||
file_content = file_content.split(',', 1)[1]
|
|
||||||
file_bytes = base64.b64decode(file_content)
|
|
||||||
except Exception as e:
|
|
||||||
return {
|
|
||||||
'status': False,
|
|
||||||
'msg': '文件内容解析失败, %s' % str(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
now_date = datetime.datetime.now()
|
|
||||||
date_path = now_date.strftime('%Y/%m/%d')
|
|
||||||
relative_path = '%s/%s' % (date_path, safe_file_name)
|
|
||||||
base_path = '/data'
|
|
||||||
save_dir = os.path.join(base_path, now_date.strftime('%Y'), now_date.strftime('%m'), now_date.strftime('%d'))
|
|
||||||
save_path = os.path.join(save_dir, safe_file_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not os.path.exists(save_dir):
|
|
||||||
os.makedirs(save_dir)
|
|
||||||
with open(save_path, 'wb') as f:
|
|
||||||
f.write(file_bytes)
|
|
||||||
return {
|
return {
|
||||||
'status': True,
|
'status': True,
|
||||||
'msg': 'upload success',
|
'msg': 'upload success',
|
||||||
'data': {
|
'data': {
|
||||||
'storage_type': storage_type,
|
'save_url': save_url
|
||||||
'file_name': safe_file_name,
|
|
||||||
'file_path': relative_path,
|
|
||||||
'save_path': save_path,
|
|
||||||
'file_size': len(file_bytes)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
327
b/docs/user_tenant_refactor.md
Normal file
327
b/docs/user_tenant_refactor.md
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
# 用户按租户隔离改造规划
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
当前用户注册时,手机号和用户名按 `users` 全表做唯一性判断。两个不同域名下,如果使用同一个手机号注册,会触发“该手机号已注册”。登录时也存在类似问题:手机号、用户名查询没有先限定当前域名所属租户,若数据库中出现跨域名同手机号用户,可能查到错误用户。
|
||||||
|
|
||||||
|
本次改造目标是支持“不同域名/站点下允许同手机号注册和登录”,同时不影响余额、计费、扣费、结算链路。
|
||||||
|
|
||||||
|
## 核心原则
|
||||||
|
|
||||||
|
1. 前端不传 `tenant_orgid`。
|
||||||
|
2. 后端根据当前访问域名解析 `tenant_orgid`。
|
||||||
|
3. 注册、登录、找回密码、验证码等入口按 `tenant_orgid + 手机号/用户名` 查询用户。
|
||||||
|
4. 不改 `users.id`、`users.orgid`、`organization.id`、`customer.customerid`。
|
||||||
|
5. 不改账务核心表:`account`、`ledger`、`customer`、`bz_order`、`order_goods` 等。
|
||||||
|
6. 当前数据库暂时无法新增 INDEX/UNIQUE,唯一性由代码逻辑保证。
|
||||||
|
|
||||||
|
## tenant_orgid 来源
|
||||||
|
|
||||||
|
`tenant_orgid` 表示当前站点所属租户机构 ID,由后端通过域名解析。
|
||||||
|
|
||||||
|
解析规则:
|
||||||
|
|
||||||
|
1. 从 `domain_name` 获取域名;如果只有 `url_link`,从 `url_link` 中解析域名。
|
||||||
|
2. 查询 `reseller`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select orgid
|
||||||
|
from reseller
|
||||||
|
where domain_name = '当前域名'
|
||||||
|
and del_flg = '0'
|
||||||
|
limit 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 如果查到,说明当前是分销商站点:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tenant_orgid:reseller.orgid
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 如果查不到,说明当前是主站,查询主站机构:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select id
|
||||||
|
from organization
|
||||||
|
where org_type = '0'
|
||||||
|
and del_flg = '0'
|
||||||
|
limit 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
主站场景:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tenant_orgid:主站机构id
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 数据库改造
|
||||||
|
|
||||||
|
只改 `users` 表,新增字段,不新增索引。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN tenant_orgid varchar(32) DEFAULT NULL COMMENT '注册/登录所属租户机构id';
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 历史数据回填
|
||||||
|
|
||||||
|
客户用户通常满足:`users.orgid` 是客户机构,`organization.parentid` 是该客户所属分销商或主站机构。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE users u
|
||||||
|
LEFT JOIN organization o ON u.orgid = o.id
|
||||||
|
SET u.tenant_orgid = o.parentid
|
||||||
|
WHERE u.tenant_orgid IS NULL
|
||||||
|
AND o.parentid IS NOT NULL;
|
||||||
|
|
||||||
|
# 注意管理员角色用户
|
||||||
|
```
|
||||||
|
|
||||||
|
分销商自身用户满足:`users.orgid = reseller.orgid`。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE users u
|
||||||
|
INNER JOIN reseller r ON u.orgid = r.orgid
|
||||||
|
SET u.tenant_orgid = r.orgid
|
||||||
|
WHERE u.tenant_orgid IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
主站/内部用户兜底回填为主站机构。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE users u
|
||||||
|
SET u.tenant_orgid = (
|
||||||
|
SELECT id FROM organization WHERE org_type = '0' AND del_flg = '0' LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE u.tenant_orgid IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
回填后检查:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select id, username, mobile, orgid, tenant_orgid
|
||||||
|
from users
|
||||||
|
where tenant_orgid is null;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 注册接口改造
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
b/customer/registerUser.dspy
|
||||||
|
```
|
||||||
|
|
||||||
|
改造点:
|
||||||
|
|
||||||
|
1. 注册开始先解析 `tenant_orgid`。
|
||||||
|
2. 手机号重复校验从全局校验:
|
||||||
|
|
||||||
|
```text
|
||||||
|
mobile:手机号
|
||||||
|
```
|
||||||
|
|
||||||
|
改成租户内校验:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select id
|
||||||
|
from users
|
||||||
|
where mobile = '手机号'
|
||||||
|
and tenant_orgid = '当前tenant_orgid'
|
||||||
|
and del_flg = '0'
|
||||||
|
limit 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 用户名重复校验也加 `tenant_orgid`。
|
||||||
|
2. 邮箱重复校验建议也加 `tenant_orgid`。
|
||||||
|
3. 新增 `users` 时写入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tenant_orgid:当前tenant_orgid
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 如果未传 `username`,仍可用手机号作为用户名,但唯一范围变为当前租户内。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 登录接口改造
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
b/user/loginUser.dspy
|
||||||
|
b/user/logintype.dspy
|
||||||
|
```
|
||||||
|
|
||||||
|
改造点:
|
||||||
|
|
||||||
|
1. 登录开始先解析 `tenant_orgid`。
|
||||||
|
2. 手机号验证码登录按当前租户查询:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select *
|
||||||
|
from users
|
||||||
|
where mobile = '手机号'
|
||||||
|
and tenant_orgid = '当前tenant_orgid'
|
||||||
|
and del_flg = '0'
|
||||||
|
limit 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 用户名密码登录按当前租户查询:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select *
|
||||||
|
from users
|
||||||
|
where username = '用户名'
|
||||||
|
and password = '加密后密码'
|
||||||
|
and tenant_orgid = '当前tenant_orgid'
|
||||||
|
and del_flg = '0'
|
||||||
|
limit 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. 如果使用手机号作为 `username`,也必须加 `tenant_orgid`。
|
||||||
|
2. 原有域名归属判断可以保留,但查用户时必须先限定租户,避免查到其他域名同手机号用户。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 登录失败锁定改造
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
b/user/logintype.dspy
|
||||||
|
```
|
||||||
|
|
||||||
|
当前 `login_fail_log` 按 `user_name` 记录失败次数。跨域名同手机号后,A 域名失败可能锁定 B 域名同手机号。
|
||||||
|
|
||||||
|
暂不改表时,建议使用拼接 key:
|
||||||
|
|
||||||
|
```text
|
||||||
|
login_fail_key = tenant_orgid + ':' + username_or_mobile
|
||||||
|
```
|
||||||
|
|
||||||
|
然后传给:
|
||||||
|
|
||||||
|
```text
|
||||||
|
handle_login_failed(login_fail_key)
|
||||||
|
check_login_allowed(login_fail_key)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 找回密码和验证码改造
|
||||||
|
|
||||||
|
建议同步改造:
|
||||||
|
|
||||||
|
```text
|
||||||
|
b/customer/forgotPassword.dspy
|
||||||
|
b/user/retrievecode.dspy
|
||||||
|
b/user/mobilecode.dspy
|
||||||
|
```
|
||||||
|
|
||||||
|
原因:这些接口也会通过手机号或用户名全局查 `users`。改造后应先解析 `tenant_orgid`,再按当前租户查用户。
|
||||||
|
|
||||||
|
查询条件示例:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
select *
|
||||||
|
from users
|
||||||
|
where mobile = '手机号'
|
||||||
|
and tenant_orgid = '当前tenant_orgid'
|
||||||
|
and del_flg = '0'
|
||||||
|
limit 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 其他代码排查
|
||||||
|
|
||||||
|
全局排查这些模式:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sor.R('users', {'mobile': ...})
|
||||||
|
sor.R('users', {'username': ...})
|
||||||
|
```
|
||||||
|
|
||||||
|
处理原则:
|
||||||
|
|
||||||
|
1. 注册、登录、找回密码、验证码:必须加 `tenant_orgid`。
|
||||||
|
2. 前台用户入口:优先加 `tenant_orgid`。
|
||||||
|
3. 平台后台管理查询:如果需要跨租户管理,可以不加;如果是当前站点后台,则应加。
|
||||||
|
4. 账务、订单、结算:通常按 `userid`、`orgid`、`customerid` 查询,不需要因手机号隔离改造。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 对余额、计费、扣费、结算的影响
|
||||||
|
|
||||||
|
按本规划实施,理论上不影响账务链路。
|
||||||
|
|
||||||
|
原因是账务核心链路依赖以下字段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
users.id
|
||||||
|
users.orgid
|
||||||
|
organization.id
|
||||||
|
customer.customerid
|
||||||
|
account.orgid
|
||||||
|
bz_order.customerid
|
||||||
|
bz_order.userid
|
||||||
|
```
|
||||||
|
|
||||||
|
本次只新增 `users.tenant_orgid`,并改变登录/注册时如何定位用户,不改变已有账务关联 ID。
|
||||||
|
|
||||||
|
## 并发风险
|
||||||
|
|
||||||
|
由于当前不能新增数据库唯一索引,租户内手机号唯一性只能由代码保证。
|
||||||
|
|
||||||
|
风险:
|
||||||
|
|
||||||
|
```text
|
||||||
|
两个请求同时注册同一 tenant_orgid + mobile,可能同时通过注册前校验。
|
||||||
|
```
|
||||||
|
|
||||||
|
降低风险方案:
|
||||||
|
|
||||||
|
1. 注册前查询一次。
|
||||||
|
2. 插入前尽量保持注册逻辑短。
|
||||||
|
3. 插入后再查询一次同 `tenant_orgid + mobile + del_flg = 0` 的用户数量。
|
||||||
|
4. 如果发现重复,返回异常并人工处理。
|
||||||
|
|
||||||
|
后续如果数据库允许新增唯一约束,再补数据库唯一约束。
|
||||||
|
|
||||||
|
## 建议实施顺序
|
||||||
|
|
||||||
|
1. 给 `users` 增加 `tenant_orgid` 字段。
|
||||||
|
2. 回填历史用户 `tenant_orgid`。
|
||||||
|
3. 抽出或复制一段 `tenant_orgid` 解析逻辑到注册/登录相关接口。
|
||||||
|
4. 改造 `b/customer/registerUser.dspy`。
|
||||||
|
5. 改造 `b/user/logintype.dspy`。
|
||||||
|
6. 改造 `b/user/loginUser.dspy`。
|
||||||
|
7. 改造 `b/customer/forgotPassword.dspy`。
|
||||||
|
8. 改造 `b/user/retrievecode.dspy`。
|
||||||
|
9. 改造 `b/user/mobilecode.dspy`。
|
||||||
|
10. 全局排查剩余按 `mobile`、`username` 查 `users` 的代码。
|
||||||
|
11. 测试主站注册登录。
|
||||||
|
12. 测试分销商 A 注册登录。
|
||||||
|
13. 测试分销商 B 使用同手机号注册登录。
|
||||||
|
14. 测试找回密码、验证码登录、用户名密码登录。
|
||||||
|
15. 测试余额、下单、扣费、结算查询链路是否仍按原 `orgid/customerid` 工作。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
1. 同一域名下,同手机号不能重复注册。
|
||||||
|
2. 不同域名下,同手机号可以分别注册。
|
||||||
|
3. 不同域名下,同手机号登录时只登录当前域名对应用户。
|
||||||
|
4. 登录失败锁定只影响当前域名对应用户。
|
||||||
|
5. 找回密码只影响当前域名对应用户。
|
||||||
|
6. 原有用户能正常登录。
|
||||||
|
7. 订单、余额、扣费、结算不串账。
|
||||||
|
|
||||||
@ -1,3 +1,15 @@
|
|||||||
|
async def get_reseller_user_tenant_orgid(sor, orgid):
|
||||||
|
reseller = await sor.R('reseller', {'orgid': orgid, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return orgid
|
||||||
|
org = await sor.R('organization', {'id': orgid, 'del_flg': '0'})
|
||||||
|
if len(org) >= 1 and org[0].get('parentid'):
|
||||||
|
return org[0]['parentid']
|
||||||
|
main_org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(main_org) >= 1:
|
||||||
|
return main_org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def reseller_add_user(ns):
|
async def reseller_add_user(ns):
|
||||||
"""
|
"""
|
||||||
添加用户
|
添加用户
|
||||||
@ -15,11 +27,16 @@ async def reseller_add_user(ns):
|
|||||||
try:
|
try:
|
||||||
ns['id'] = uuid()
|
ns['id'] = uuid()
|
||||||
ns['password'] = password_encode(ns['password'])
|
ns['password'] = password_encode(ns['password'])
|
||||||
user = await sor.R('users', {'username': ns['username']})
|
tenant_orgid = await get_reseller_user_tenant_orgid(sor, ns.get('orgid'))
|
||||||
|
if not tenant_orgid:
|
||||||
|
return {'status': False, 'msg': '未找到用户所属租户'}
|
||||||
|
user_sql = """select id from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns['username'], tenant_orgid)
|
||||||
|
user = await sor.sqlExe(user_sql, {})
|
||||||
if len(user) >= 1:
|
if len(user) >= 1:
|
||||||
return {'status': False, 'msg': '用户名重复'}
|
return {'status': False, 'msg': '用户名重复'}
|
||||||
rolereacs = await sor.R('role', {'role': '管理员', 'del_flg': '0', 'org_type': '1'})
|
rolereacs = await sor.R('role', {'role': '管理员', 'del_flg': '0', 'org_type': '1'})
|
||||||
ns['user_reseller'] = '1'
|
ns['user_reseller'] = '1'
|
||||||
|
ns['tenant_orgid'] = tenant_orgid
|
||||||
await sor.C('users', ns)
|
await sor.C('users', ns)
|
||||||
await sor.C('userrole', {'userid': ns['id'], 'roleid': rolereacs[0]['id'], 'id': uuid()})
|
await sor.C('userrole', {'userid': ns['id'], 'roleid': rolereacs[0]['id'], 'id': uuid()})
|
||||||
return {'status': True, 'msg': '用户添加成功', 'userid': ns['id']}
|
return {'status': True, 'msg': '用户添加成功', 'userid': ns['id']}
|
||||||
|
|||||||
@ -1,3 +1,15 @@
|
|||||||
|
async def get_user_tenant_orgid(sor, orgid):
|
||||||
|
reseller = await sor.R('reseller', {'orgid': orgid, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return orgid
|
||||||
|
org = await sor.R('organization', {'id': orgid, 'del_flg': '0'})
|
||||||
|
if len(org) >= 1 and org[0].get('parentid'):
|
||||||
|
return org[0]['parentid']
|
||||||
|
main_org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(main_org) >= 1:
|
||||||
|
return main_org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def addUser(ns):
|
async def addUser(ns):
|
||||||
"""
|
"""
|
||||||
添加用户
|
添加用户
|
||||||
@ -17,11 +29,16 @@ async def addUser(ns):
|
|||||||
else:
|
else:
|
||||||
user_reseller = '0'
|
user_reseller = '0'
|
||||||
ns['password'] = password_encode(ns['password'])
|
ns['password'] = password_encode(ns['password'])
|
||||||
user = await sor.R('users', {'username': ns['username']})
|
tenant_orgid = await get_user_tenant_orgid(sor, ns.get('orgid'))
|
||||||
|
if not tenant_orgid:
|
||||||
|
return {'status': False, 'msg': '未找到用户所属租户'}
|
||||||
|
user_sql = """select id from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns['username'], tenant_orgid)
|
||||||
|
user = await sor.sqlExe(user_sql, {})
|
||||||
if len(user) >= 1:
|
if len(user) >= 1:
|
||||||
return {'status': False, 'msg': '用户名重复'}
|
return {'status': False, 'msg': '用户名重复'}
|
||||||
ns['id'] = uuid()
|
ns['id'] = uuid()
|
||||||
if not ns.get('type'):
|
if not ns.get('type'):
|
||||||
|
ns['tenant_orgid'] = tenant_orgid
|
||||||
await sor.C('users', ns)
|
await sor.C('users', ns)
|
||||||
return {'status': True, 'msg': '添加成功', 'userid': ns['id']}
|
return {'status': True, 'msg': '添加成功', 'userid': ns['id']}
|
||||||
rolereacs = await sor.R('role', {'role': '管理员', 'del_flg': '0', 'org_type': '0'})
|
rolereacs = await sor.R('role', {'role': '管理员', 'del_flg': '0', 'org_type': '0'})
|
||||||
@ -30,6 +47,7 @@ async def addUser(ns):
|
|||||||
return {'status': False, 'msg': '已有管理员'}
|
return {'status': False, 'msg': '已有管理员'}
|
||||||
ns['orgid'] = 'mIWUHBeeDM8mwAFPIQ8pS'
|
ns['orgid'] = 'mIWUHBeeDM8mwAFPIQ8pS'
|
||||||
ns['user_reseller'] = user_reseller
|
ns['user_reseller'] = user_reseller
|
||||||
|
ns['tenant_orgid'] = ns['orgid']
|
||||||
await sor.C('users', ns)
|
await sor.C('users', ns)
|
||||||
ns['orgid'] = 'mIWUHBeeDM8mwAFPIQ8pS'
|
ns['orgid'] = 'mIWUHBeeDM8mwAFPIQ8pS'
|
||||||
await sor.C('userrole', {'userid': ns['id'], 'roleid': rolereacs[0]['id'], 'id': uuid()})
|
await sor.C('userrole', {'userid': ns['id'], 'roleid': rolereacs[0]['id'], 'id': uuid()})
|
||||||
|
|||||||
@ -57,6 +57,21 @@ async def check_wechat_login_status(code):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
async def get_login_tenant_orgid(sor, ns):
|
||||||
|
domain_name = ns.get('domain_name')
|
||||||
|
if (not domain_name) and ns.get('url_link'):
|
||||||
|
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
||||||
|
if domain_name:
|
||||||
|
domain_name = domain_name.strip().replace('https://', '').replace('http://', '').replace('/', '')
|
||||||
|
if 'localhost' in domain_name:
|
||||||
|
domain_name = 'dev.opencomputing.cn'
|
||||||
|
reseller = await sor.R('reseller', {'domain_name': domain_name, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return reseller[0]['orgid']
|
||||||
|
org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(org) >= 1:
|
||||||
|
return org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def loginUser(ns):
|
async def loginUser(ns):
|
||||||
"""
|
"""
|
||||||
@ -65,6 +80,9 @@ async def loginUser(ns):
|
|||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
if ns:
|
if ns:
|
||||||
|
tenant_orgid = await get_login_tenant_orgid(sor, ns)
|
||||||
|
if not tenant_orgid and ns.get('username') not in ['admin', '开元云(北京)科技有限公司']:
|
||||||
|
return {'status': False, 'msg': '未找到当前域名所属租户'}
|
||||||
# 处理微信授权后的登录
|
# 处理微信授权后的登录
|
||||||
if ns.get('wechatCode'): # 微信扫码登录后的`code`
|
if ns.get('wechatCode'): # 微信扫码登录后的`code`
|
||||||
wechat_user_info = await check_wechat_login_status(ns.get('wechatCode'))
|
wechat_user_info = await check_wechat_login_status(ns.get('wechatCode'))
|
||||||
@ -91,7 +109,8 @@ async def loginUser(ns):
|
|||||||
if wechat_user_info:
|
if wechat_user_info:
|
||||||
openid_f = wechat_user_info['openid']
|
openid_f = wechat_user_info['openid']
|
||||||
# 查询数据库,查看openid是否已绑定账户
|
# 查询数据库,查看openid是否已绑定账户
|
||||||
userreacs = await sor.R('users', {'wechat_openid': openid_f, 'del_flg': '0'})
|
wechat_sql = """select * from users where wechat_openid = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (openid_f, tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(wechat_sql, {})
|
||||||
if len(userreacs) >= 1:
|
if len(userreacs) >= 1:
|
||||||
# 如果openid绑定了用户, 查找用户名和密码 使用账号密码登录
|
# 如果openid绑定了用户, 查找用户名和密码 使用账号密码登录
|
||||||
ns['username'] = userreacs[0]['username']
|
ns['username'] = userreacs[0]['username']
|
||||||
@ -152,10 +171,12 @@ async def loginUser(ns):
|
|||||||
type = 0
|
type = 0
|
||||||
if type1 == 1:
|
if type1 == 1:
|
||||||
# 手机号验证码登录
|
# 手机号验证码登录
|
||||||
userreacs = await sor.R('users', {'mobile': ns.get('mobile')})
|
user_sql = """select * from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('mobile'), tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
|
|
||||||
if not userreacs:
|
if not userreacs:
|
||||||
userreacs = await sor.R('users', {'username': ns.get('username')})
|
user_sql = """select * from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
|
|
||||||
# 如果是微信扫码后绑定已有账号
|
# 如果是微信扫码后绑定已有账号
|
||||||
if ns.get('wechat_openid'):
|
if ns.get('wechat_openid'):
|
||||||
@ -170,14 +191,17 @@ async def loginUser(ns):
|
|||||||
else:
|
else:
|
||||||
ns['password'] = password_encode(ns['password'])
|
ns['password'] = password_encode(ns['password'])
|
||||||
ns['del_flg'] = '0'
|
ns['del_flg'] = '0'
|
||||||
userreacs = await sor.R('users', {'username': ns.get('username'), 'password': ns.get('password')})
|
if ns.get('username') in ['admin', '开元云(北京)科技有限公司']:
|
||||||
|
userreacs = await sor.R('users', {'username': ns.get('username'), 'password': ns.get('password'), 'del_flg': '0'})
|
||||||
|
else:
|
||||||
|
user_sql = """select * from users where username = '%s' and password = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), ns.get('password'), tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
if len(userreacs) >= 1:
|
if len(userreacs) >= 1:
|
||||||
type += 1
|
type += 1
|
||||||
await remember_user(userreacs[0]['id'], username=userreacs[0]['username'], userorgid=userreacs[0]['orgid'])
|
await remember_user(userreacs[0]['id'], username=userreacs[0]['username'], userorgid=userreacs[0]['orgid'])
|
||||||
else:
|
else:
|
||||||
ns['mobile'] = ns['username']
|
user_sql = """select * from users where mobile = '%s' and password = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), ns.get('password'), tenant_orgid)
|
||||||
ns.pop('username')
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
userreacs = await sor.R('users', ns)
|
|
||||||
if len(userreacs) >= 1:
|
if len(userreacs) >= 1:
|
||||||
type += 1
|
type += 1
|
||||||
await remember_user(userreacs[0]['id'], username=userreacs[0]['username'], userorgid=userreacs[0]['orgid'])
|
await remember_user(userreacs[0]['id'], username=userreacs[0]['username'], userorgid=userreacs[0]['orgid'])
|
||||||
|
|||||||
@ -93,6 +93,22 @@ async def check_login_allowed(user_name):
|
|||||||
print("exception:", user_name, e)
|
print("exception:", user_name, e)
|
||||||
return {'status': False, 'msg': '登录操作失败, %s' % str(e)}
|
return {'status': False, 'msg': '登录操作失败, %s' % str(e)}
|
||||||
|
|
||||||
|
async def get_login_tenant_orgid(sor, ns):
|
||||||
|
domain_name = ns.get('domain_name')
|
||||||
|
if (not domain_name) and ns.get('url_link'):
|
||||||
|
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
||||||
|
if domain_name:
|
||||||
|
domain_name = domain_name.strip().replace('https://', '').replace('http://', '').replace('/', '')
|
||||||
|
if 'localhost' in domain_name:
|
||||||
|
domain_name = 'dev.opencomputing.cn'
|
||||||
|
reseller = await sor.R('reseller', {'domain_name': domain_name, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return reseller[0]['orgid']
|
||||||
|
org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(org) >= 1:
|
||||||
|
return org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def logintype(ns):
|
async def logintype(ns):
|
||||||
"""
|
"""
|
||||||
1、判断用户是否为主级(如果在reseller没要找到数据,证明就是主级)
|
1、判断用户是否为主级(如果在reseller没要找到数据,证明就是主级)
|
||||||
@ -140,11 +156,16 @@ async def logintype(ns):
|
|||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
|
|
||||||
domain_name = ns.get('domain_name')
|
domain_name = ns.get('domain_name')
|
||||||
|
tenant_orgid = await get_login_tenant_orgid(sor, ns)
|
||||||
|
if not tenant_orgid and ns.get('username') != 'admin':
|
||||||
|
return {'status': False, 'msg': '未找到当前域名所属租户'}
|
||||||
|
login_name = ns.get('username') or ns.get('mobile') or ''
|
||||||
|
login_fail_key = '%s:%s' % (tenant_orgid, login_name)
|
||||||
# if domain_name in ['www.opencomputing.cn', 'dev.opencomputing.cn', 'localhost:9527', 'www.ncmatch.cn'] and ns.get('username') not in ['开元云(北京)科技有限公司', 'admin', 'kyy_root', 'kyy_kaiyuan', 'kyacloud', 'kyy_运营', 'kyy_销售', 'kyy_财务', '测试用户', 'kycloud']:
|
# if domain_name in ['www.opencomputing.cn', 'dev.opencomputing.cn', 'localhost:9527', 'www.ncmatch.cn'] and ns.get('username') not in ['开元云(北京)科技有限公司', 'admin', 'kyy_root', 'kyy_kaiyuan', 'kyacloud', 'kyy_运营', 'kyy_销售', 'kyy_财务', '测试用户', 'kycloud']:
|
||||||
if domain_name in ['www.opencomputing.cn', 'dev.opencomputing.cn', 'localhost:9527', 'www.ncmatch.cn'] and not ns.get('username') and not ns.get('codeid'):
|
if domain_name in ['www.opencomputing.cn', 'dev.opencomputing.cn', 'localhost:9527', 'www.ncmatch.cn'] and not ns.get('username') and not ns.get('codeid'):
|
||||||
|
|
||||||
# 登录失败次数限制
|
# 登录失败次数限制
|
||||||
login_allowed = await check_login_allowed(ns.get('username'))
|
login_allowed = await check_login_allowed(login_fail_key)
|
||||||
if not login_allowed.get('status'):
|
if not login_allowed.get('status'):
|
||||||
return {'status': False, 'msg': login_allowed.get('msg')}
|
return {'status': False, 'msg': login_allowed.get('msg')}
|
||||||
|
|
||||||
@ -153,7 +174,8 @@ async def logintype(ns):
|
|||||||
'status': False,
|
'status': False,
|
||||||
'msg': '请输入手机号'
|
'msg': '请输入手机号'
|
||||||
}
|
}
|
||||||
real_mobile_li = await sor.R('users', {'username': ns['username']})
|
real_mobile_sql = """select * from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), tenant_orgid)
|
||||||
|
real_mobile_li = await sor.sqlExe(real_mobile_sql, {})
|
||||||
if not real_mobile_li:
|
if not real_mobile_li:
|
||||||
return {
|
return {
|
||||||
'status': False,
|
'status': False,
|
||||||
@ -179,13 +201,14 @@ async def logintype(ns):
|
|||||||
}
|
}
|
||||||
code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')})
|
code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')})
|
||||||
if len(code) < 1:
|
if len(code) < 1:
|
||||||
await handle_login_failed(ns.get('username'))
|
await handle_login_failed(login_fail_key)
|
||||||
return {'status': False, 'msg': '验证码不正确'}
|
return {'status': False, 'msg': '验证码不正确'}
|
||||||
|
|
||||||
password = password_encode(ns['password'])
|
password = password_encode(ns['password'])
|
||||||
users = await sor.R('users', {'username': ns.get('username'), 'password': password})
|
users_sql = """select * from users where username = '%s' and password = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), password, tenant_orgid)
|
||||||
|
users = await sor.sqlExe(users_sql, {})
|
||||||
if len(users) < 1:
|
if len(users) < 1:
|
||||||
await handle_login_failed(ns.get('username'))
|
await handle_login_failed(login_fail_key)
|
||||||
return {"status": False,'msg':'用户名或密码错误'}
|
return {"status": False,'msg':'用户名或密码错误'}
|
||||||
|
|
||||||
return {'status': True}
|
return {'status': True}
|
||||||
@ -199,14 +222,20 @@ async def logintype(ns):
|
|||||||
return {'status': False, 'msg': '验证码不正确'}
|
return {'status': False, 'msg': '验证码不正确'}
|
||||||
if type == 1:
|
if type == 1:
|
||||||
# 手机号登录
|
# 手机号登录
|
||||||
users = await sor.R('users', {'mobile': ns.get('mobile')})
|
users_sql = """select * from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('mobile'), tenant_orgid)
|
||||||
|
users = await sor.sqlExe(users_sql, {})
|
||||||
if not users:
|
if not users:
|
||||||
users = await sor.R('users', {'username': ns.get('username')})
|
users_sql = """select * from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), tenant_orgid)
|
||||||
|
users = await sor.sqlExe(users_sql, {})
|
||||||
else:
|
else:
|
||||||
password = password_encode(ns['password'])
|
password = password_encode(ns['password'])
|
||||||
users = await sor.R('users', {'username': ns.get('username'), 'password': password})
|
if ns.get('username') == 'admin':
|
||||||
|
users = await sor.R('users', {'username': ns.get('username'), 'password': password})
|
||||||
|
else:
|
||||||
|
users_sql = """select * from users where username = '%s' and password = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (ns.get('username'), password, tenant_orgid)
|
||||||
|
users = await sor.sqlExe(users_sql, {})
|
||||||
if len(users) < 1:
|
if len(users) < 1:
|
||||||
await handle_login_failed(ns.get('username'))
|
await handle_login_failed(login_fail_key)
|
||||||
return {"status": False,'msg':'用户名或密码错误'}
|
return {"status": False,'msg':'用户名或密码错误'}
|
||||||
elif ns.get('username') == "admin":
|
elif ns.get('username') == "admin":
|
||||||
return {'status': True}
|
return {'status': True}
|
||||||
|
|||||||
@ -65,10 +65,30 @@ async def handle_login_failed(mobile: str) -> bool:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def get_mobilecode_tenant_orgid(sor, ns):
|
||||||
|
domain_name = ns.get('domain_name')
|
||||||
|
if (not domain_name) and ns.get('url_link'):
|
||||||
|
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
||||||
|
if domain_name:
|
||||||
|
domain_name = domain_name.strip().replace('https://', '').replace('http://', '').replace('/', '')
|
||||||
|
if 'localhost' in domain_name:
|
||||||
|
domain_name = 'dev.opencomputing.cn'
|
||||||
|
reseller = await sor.R('reseller', {'domain_name': domain_name, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return reseller[0]['orgid']
|
||||||
|
org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(org) >= 1:
|
||||||
|
return org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def mobilecode(ns):
|
async def mobilecode(ns):
|
||||||
"""发送短信验证码,支持注册和登录筛选"""
|
"""发送短信验证码,支持注册和登录筛选"""
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
|
tenant_orgid = await get_mobilecode_tenant_orgid(sor, ns)
|
||||||
|
if not tenant_orgid:
|
||||||
|
return {'status': False, 'msg': '未找到当前域名所属租户'}
|
||||||
|
|
||||||
# 获取操作类型:register 或 login
|
# 获取操作类型:register 或 login
|
||||||
action_type = ns.get('action_type') # register 或 login
|
action_type = ns.get('action_type') # register 或 login
|
||||||
|
|
||||||
@ -81,11 +101,13 @@ async def mobilecode(ns):
|
|||||||
return {'status': False, 'msg': '手机号不能为空'}
|
return {'status': False, 'msg': '手机号不能为空'}
|
||||||
|
|
||||||
# 检查短信发送限制
|
# 检查短信发送限制
|
||||||
can_send = await handle_login_failed(mobile)
|
sms_limit_key = '%s:%s' % (tenant_orgid[:8], mobile)
|
||||||
|
can_send = await handle_login_failed(sms_limit_key)
|
||||||
if not can_send:
|
if not can_send:
|
||||||
return {'status': False, 'msg': '发送过于频繁,请10分钟后再试'}
|
return {'status': False, 'msg': '发送过于频繁,请10分钟后再试'}
|
||||||
|
|
||||||
userreacs = await sor.R('users', {'mobile': mobile, 'del_flg': '0'})
|
user_sql = """select * from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (mobile, tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
|
|
||||||
# 注册逻辑:检查手机号是否已存在
|
# 注册逻辑:检查手机号是否已存在
|
||||||
if action_type == 'register':
|
if action_type == 'register':
|
||||||
@ -128,12 +150,14 @@ async def mobilecode(ns):
|
|||||||
# 原有逻辑:如果没有指定action_type,保持原有逻辑
|
# 原有逻辑:如果没有指定action_type,保持原有逻辑
|
||||||
else:
|
else:
|
||||||
type = 0
|
type = 0
|
||||||
ns['del_flg'] = '0'
|
username = ns.get('username')
|
||||||
userreacs = await sor.R('users', ns)
|
user_sql = """select * from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (username, tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
if len(userreacs) >= 1:
|
if len(userreacs) >= 1:
|
||||||
type += 1
|
type += 1
|
||||||
else:
|
else:
|
||||||
userreacs = await sor.R('users', {'mobile': ns['username'], 'del_flg': '0'})
|
user_sql = """select * from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (username, tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
if len(userreacs) >= 1:
|
if len(userreacs) >= 1:
|
||||||
type += 1
|
type += 1
|
||||||
if type >= 1:
|
if type >= 1:
|
||||||
|
|||||||
@ -1,14 +1,37 @@
|
|||||||
|
async def get_retrieve_tenant_orgid(sor, ns):
|
||||||
|
domain_name = ns.get('domain_name')
|
||||||
|
if (not domain_name) and ns.get('url_link'):
|
||||||
|
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
||||||
|
if domain_name:
|
||||||
|
domain_name = domain_name.strip().replace('https://', '').replace('http://', '').replace('/', '')
|
||||||
|
if 'localhost' in domain_name:
|
||||||
|
domain_name = 'dev.opencomputing.cn'
|
||||||
|
reseller = await sor.R('reseller', {'domain_name': domain_name, 'del_flg': '0'})
|
||||||
|
if len(reseller) >= 1:
|
||||||
|
return reseller[0]['orgid']
|
||||||
|
org = await sor.R('organization', {'org_type': '0', 'del_flg': '0'})
|
||||||
|
if len(org) >= 1:
|
||||||
|
return org[0]['id']
|
||||||
|
return None
|
||||||
|
|
||||||
async def retrievecode(ns):
|
async def retrievecode(ns):
|
||||||
"""找回密码发送短信验证码"""
|
"""找回密码发送短信验证码"""
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
|
tenant_orgid = await get_retrieve_tenant_orgid(sor, ns)
|
||||||
|
if not tenant_orgid:
|
||||||
|
return {'status': False, 'msg': '未找到当前域名所属租户'}
|
||||||
type = 0
|
type = 0
|
||||||
ns['del_flg'] = '0'
|
username = ns.get('username') or ns.get('mobile')
|
||||||
userreacs = await sor.R('users', ns)
|
if not username:
|
||||||
|
return {'status': False, 'msg': '用户标识不能为空'}
|
||||||
|
user_sql = """select * from users where username = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (username, tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
if len(userreacs) >= 1:
|
if len(userreacs) >= 1:
|
||||||
type += 1
|
type += 1
|
||||||
else:
|
else:
|
||||||
userreacs = await sor.R('users', {'mobile': ns['username'], 'del_flg': '0'})
|
user_sql = """select * from users where mobile = '%s' and tenant_orgid = '%s' and del_flg = '0' limit 1;""" % (username, tenant_orgid)
|
||||||
|
userreacs = await sor.sqlExe(user_sql, {})
|
||||||
if len(userreacs) >= 1:
|
if len(userreacs) >= 1:
|
||||||
type += 1
|
type += 1
|
||||||
if type >= 1:
|
if type >= 1:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user