跳到主要内容

状态

状态

  • 状态,用户当前的状态,用于触发功能逻辑
  • 以 id,user,status,info 的形式存储于 user_status 表里,其中 status+info 为唯一键
  • 状态可以设置成状态机,搭配复杂的功能逻辑
  • 状态同时可设置信息属性
    • 用于传递消息,如在不同逻辑之间传递信息(发送 A 消息,发送 B 消息,返回 A+B 消息,可在状态信息中记录 A 消息,B 消息查找状态信息获取 A 消息,合并发送)
    • 用于减少状态,同状态下可将信息分阶段表示,如状态 A 不变,信息由 1-> 2,节省指令条数以及函数逻辑
    • 复杂的用户+状态逻辑也可以使用队列(参考验证码队列

状态设计

  • 可以给同一状态设置多个信息,比如说答题活动,如果给每个题都设置一个状态(答题_1,答题_2),添加和判断起来都很麻烦,且不容易合并(两个平台都在答题,一个答题_2,一个答题_10,不好合并)
  • 改成状态"答题",信息为"1-n"可以合并(合并时如果是数字取大的),且减少前端状态表中的数据,减少指令的配置项(command 页)以及函数
  • 按照此方式设计好状态后,在 kind.yaml 中新增状态,前端即可使用

状态查找

  • 有些消息无用户,返回空
  • 从 user_platform 表中查找用户 id
  • 若无状态,返回状态列表
  • 若有状态
    • 返回信息

状态用户查找

  • 查找表中拥有此状态的用户 id 列表
  • 根据平台返回对应的平台 id 列表

状态添加

  • 根据平台和平台 id 查找用户 id
  • json 格式化信息后插入表中

状态删除

  • 根据平台和平台 id 查找用户 id
  • 删除对应状态
  • 若该用户无状态,也不能删除 user_platform 中的用户,否则会导致绑定失效

user_status 表

CREATE TABLE IF NOT EXISTS user_status (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
status VARCHAR(64),
info JSON,
UNIQUE KEY ux_user_status (user_id, status),
INDEX ix_status (status),
CONSTRAINT fk_user_status_user FOREIGN KEY (user_id) REFERENCES user_platform(id) ON DELETE CASCADE
)

平台

平台设计

  • 使用 user_platform 将用户唯一化,其中 id 为用户唯一 id,lr5921、lr232、wechat、bili 分别为四个平台 id
  • 绑定时将两行进行合并
  • 本系统中以 LR5921 为主平台,绑定围绕其进行,也可以自己修改 status_platform_bind 实现其他逻辑

平台绑定

  • 如果平台为 LR5921、或者转换后两行一样
    • 返回失败提示(其实产生了系统异常)
  • 使用平台 id 查找用户 id
  • 若两个用户 id 存在相同的平台
    • 返回绑定错误,平台已绑定
    • 更新信息/删除状态
  • 合并用户状态(先合并平台会报唯一键错误;先删平台会同步删用户状态)
    • 将其他平台状态合并到 LR5921
    • 信息转换为数字比较大小;若不为数字采用 LR5921 的信息
    • 删除其他平台用户及对应状态
    • 对于 LR5921 没有的状态,添加进去(有点多次一举,因为系统中除 LR5921 外的两个平台没有相互绑定,此代码可以解决相互绑定的情况)

其他

废弃的状态-信息列表代码,混杂了状态与平台

状态添加原逻辑解释

  • 绑定状态
    • 由于绑定平台时会调用qq,qq 号;平台,平台号两次状态设置,故只在后者调用时进行同步
    • 添加 qq 状态时直接添加qq,qq 号
    • 添加其他状态时
      • 先添加平台,平台号状态
      • 根据 user 查询 LR5921 的状态(如果 LR5921 绑定了其他平台,此时状态也应该是一致的)
      • 根据平台号查询对应平台的状态
      • 分离两个状态列表中的绑定状态与普通状态,得到 8 个列表(两个平台两种状态及信息)
      • 合并普通状态及信息
        • 先把平台状态列表中多的状态合并到 LR5921 状态及信息列表中
        • 随后进行信息同步
          • 对于平台状态列表中的状态,信息
          • 如果能转换成数据
          • 找到 LR5921 状态列表的对应位置
          • 将 LR5921 中对应信息转换为数据
          • 比较,取大值
      • 使用 LR5921 的绑定状态列表获取对应平台
      • 对于 qq
        • 写入 qq 的绑定状态+合并后的普通状态
      • 对于其他平台
        • 写入平台的绑定状态(均为 qq,qq 号)+合并后的普通状态
  • 普通状态
    • 查找该用户是否存在 qq 状态
    • 不存在,则为 LR5921 平台,或其他未绑定账号
      • 查找其他平台状态(其他平台的未绑定账号不会有其他平台账号)
      • 对于所有平台,添加普通状态
    • 存在,则为其他平台
      • 将用户名改为 qq
      • 查找其他平台状态(qq 及绑定的所有平台)
      • 对于所有平台,添加普通状态
"""用户状态相关"""

import json

from config import database_query, database_update


async def status_check(user=None, status=None):
"""查找状态,
填写用户,返回状态列表或状态对应的信息
只填状态,返回该状态的用户
"""
if user:
user = str(user)
result = await database_query(
"SELECT status, information FROM user_status WHERE user = %s", (user,)
)

if not result:
return []

current_status = json.loads(result[0]["status"]) if result[0]["status"] else []
current_info = (
json.loads(result[0]["information"]) if result[0]["information"] else []
)

if status is None:
return current_status # 只查询状态列表

if status in current_status:
index = current_status.index(status)
return current_info[index] # 返回对应的信息

return None # 状态不存在
else:
query = "SELECT user, status FROM user_status"
result = await database_query(query)

matched_users = []
for row in result:
status_list = json.loads(row["status"]) if row["status"] else []

if status is None:
matched_users.append(row["user"]) # 全部用户
elif status in status_list:
matched_users.append(row["user"]) # 匹配状态的用户

return matched_users


async def status_add(user, status, information="无信息"):
"""添加用户状态,自动同步到各平台"""
user = str(user)
if status == "qq":
await status_edit(user, status, information)
return
elif status in ["LR232", "WECHAT", "BILI", "QQAPP"]:
await status_edit(user, status, information)

def extract_base_and_merged(result):
"""分离绑定状态与其他状态"""
base_sta, base_inf, merged_sta, merged_inf = [], [], [], []
if result:
s_list = json.loads(result[0]["status"]) or []
i_list = json.loads(result[0]["information"]) or []
for s, i in zip(s_list, i_list):
if s in ["qq", "LR232", "WECHAT", "BILI", "QQAPP"]:
base_sta.append(s)
base_inf.append(i)
else:
merged_sta.append(s)
merged_inf.append(i)
return base_sta, base_inf, merged_sta, merged_inf

# 此处进行平台绑定状态合并
qq_result = await database_query(
"SELECT status, information FROM user_status WHERE user = %s", (user,)
)
platform_result = await database_query(
"SELECT status, information FROM user_status WHERE user = %s", (str(information),)
)

# 分离绑定状态与普通状态
qq_status, qq_info, status_1, info_1 = extract_base_and_merged(qq_result)
platform_status, platform_info, status_2, info_2 = extract_base_and_merged(platform_result)

# 合并普通状态
for s1, i1 in zip(status_2, info_2):
if s1 not in status_1:
status_1.append(s1)
info_1.append(i1)

# 合并普通状态信息(均为数字则取大的)
for s2, i2 in zip(status_2, info_2):
try:
val_2 = float(i2)
idx = status_1.index(s2)
i1 = info_1[idx]
val_1 = float(i1)
if val_2 > val_1:
info_1[idx] = i2
except (ValueError, TypeError):
continue

write_data = [
(user, qq_status, qq_info),
(str(information), platform_status, platform_info),
]

for idx, platform in enumerate(qq_status):
# 已绑定平台同步绑定状态
write_data.append((qq_info[idx], platform_status, platform_info))

# 写入,绑定状态(base)+基础状态(status_1)
for u, base_status, base_info in write_data:
final_status = base_status + status_1
final_info = base_info + info_1

await database_update(
"""
INSERT INTO user_status (user, status, information)
VALUES (%s, %s, %s) AS new
ON DUPLICATE KEY UPDATE
status = new.status,
information = new.information
""",
(
u,
json.dumps(final_status),
json.dumps(final_info),
),
)

return

qq = await status_check(user, "qq")
if not qq: # 不存在 qq 状态,则为 LR5921 或其他未绑定账号
qq = user
bind_list = [qq]
for platform in ["LR232", "WECHAT", "BILI", "QQAPP"]:
bind_id = await status_check(qq, platform)
if bind_id:
bind_list.append(bind_id)
for bind_source in bind_list:
await status_edit(bind_source, status, information)


async def status_delete(user, status):
"""删除状态,自动同步到各平台"""
user = str(user)
if status in ["LR232", "WECHAT", "BILI", "QQAPP"]:
bind_user = await status_check(user, status)
await status_edit(user, status, None)
await status_edit(bind_user, "qq", None)
return
elif status == "qq":
bind_user = await status_check(user, status)
for platform in ["LR232", "WECHAT", "BILI", "QQAPP"]:
bind_id = await status_check(bind_user, platform)
if bind_id == user:
await status_edit(bind_user, platform, None)

qq = await status_check(user, "qq")
if not qq:
qq = user
bind_list = [qq]
for platform in ["LR232", "WECHAT", "BILI", "QQAPP"]:
bind_id = await status_check(qq, platform)
if bind_id:
bind_list.append(bind_id)
for bind_source in bind_list:
await status_edit(bind_source, status, None)


async def status_edit(user, status, information=None):
"""添加/更新/删除用户状态"""
if not user:
return
user = str(user)
result = await database_query(
"SELECT status, information FROM user_status WHERE user = %s", (user,)
)

if result:
current_status = json.loads(result[0]["status"]) if result[0]["status"] else []
current_info = (
json.loads(result[0]["information"]) if result[0]["information"] else []
)
if len(current_status) != len(current_info):
min_len = min(len(current_status), len(current_info))
current_status = current_status[:min_len]
current_info = current_info[:min_len]
else:
current_status, current_info = [], []

if information is None:
# 删除
if status in current_status:
index = current_status.index(status)
current_status.pop(index)
current_info.pop(index)
else:
return
else:
# 添加/更新
if status in current_status:
index = current_status.index(status)
current_info[index] = information
else:
current_status.append(status)
current_info.append(information)

# 更新数据库
if current_status:
await database_update(
"""
INSERT INTO user_status (user, status, information)
VALUES (%s, %s, %s) AS new
ON DUPLICATE KEY UPDATE
status = new.status,
information = new.information
""",
(
user,
json.dumps(current_status),
json.dumps(current_info),
),
)
else: # 如果状态已清空,删除记录
await database_update("DELETE FROM user_status WHERE user = %s", (user,))