Add parent WeChat identity mapping
This commit is contained in:
@@ -43,6 +43,222 @@ class ParentDAO(BaseDAO):
|
|||||||
)
|
)
|
||||||
).mappings().first()
|
).mappings().first()
|
||||||
|
|
||||||
|
async def get_by_unionid(self, unionid: str) -> Optional[Mapping]:
|
||||||
|
return (
|
||||||
|
await self.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
p.*,
|
||||||
|
COALESCE(binding_counts.cnt, 0) AS active_binding_count,
|
||||||
|
COALESCE(relation_counts.cnt, 0) AS active_relation_count,
|
||||||
|
COALESCE(family_counts.cnt, 0) AS active_family_count
|
||||||
|
FROM parents AS p
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT owner_user_id AS user_id, COUNT(*) AS cnt
|
||||||
|
FROM device_bindings
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY owner_user_id
|
||||||
|
) AS binding_counts
|
||||||
|
ON binding_counts.user_id = p.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT user_id, COUNT(*) AS cnt
|
||||||
|
FROM parent_child_relations
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY user_id
|
||||||
|
) AS relation_counts
|
||||||
|
ON relation_counts.user_id = p.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT user_id, COUNT(*) AS cnt
|
||||||
|
FROM device_family_members
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY user_id
|
||||||
|
) AS family_counts
|
||||||
|
ON family_counts.user_id = p.user_id
|
||||||
|
WHERE p.unionid = :unionid
|
||||||
|
AND p.status = 1
|
||||||
|
ORDER BY
|
||||||
|
(
|
||||||
|
COALESCE(binding_counts.cnt, 0)
|
||||||
|
+ COALESCE(relation_counts.cnt, 0)
|
||||||
|
+ COALESCE(family_counts.cnt, 0)
|
||||||
|
) DESC,
|
||||||
|
p.user_id ASC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
{"unionid": unionid},
|
||||||
|
)
|
||||||
|
).mappings().first()
|
||||||
|
|
||||||
|
async def get_wechat_identity_by_openid(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
app_id: str,
|
||||||
|
account_type: str,
|
||||||
|
openid: str,
|
||||||
|
) -> Optional[Mapping]:
|
||||||
|
return (
|
||||||
|
await self.execute(
|
||||||
|
"""
|
||||||
|
SELECT pwi.*, p.status AS parent_status
|
||||||
|
FROM parent_wechat_identities AS pwi
|
||||||
|
JOIN parents AS p
|
||||||
|
ON p.user_id = pwi.user_id
|
||||||
|
WHERE pwi.app_id = :app_id
|
||||||
|
AND pwi.account_type = :account_type
|
||||||
|
AND pwi.openid = :openid
|
||||||
|
AND p.status = 1
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
{"app_id": app_id, "account_type": account_type, "openid": openid},
|
||||||
|
)
|
||||||
|
).mappings().first()
|
||||||
|
|
||||||
|
async def get_wechat_identity_by_unionid(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
account_type: str,
|
||||||
|
unionid: str,
|
||||||
|
) -> Optional[Mapping]:
|
||||||
|
return (
|
||||||
|
await self.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
pwi.*,
|
||||||
|
COALESCE(binding_counts.cnt, 0) AS active_binding_count,
|
||||||
|
COALESCE(relation_counts.cnt, 0) AS active_relation_count,
|
||||||
|
COALESCE(family_counts.cnt, 0) AS active_family_count
|
||||||
|
FROM parent_wechat_identities AS pwi
|
||||||
|
JOIN parents AS p
|
||||||
|
ON p.user_id = pwi.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT owner_user_id AS user_id, COUNT(*) AS cnt
|
||||||
|
FROM device_bindings
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY owner_user_id
|
||||||
|
) AS binding_counts
|
||||||
|
ON binding_counts.user_id = pwi.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT user_id, COUNT(*) AS cnt
|
||||||
|
FROM parent_child_relations
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY user_id
|
||||||
|
) AS relation_counts
|
||||||
|
ON relation_counts.user_id = pwi.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT user_id, COUNT(*) AS cnt
|
||||||
|
FROM device_family_members
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY user_id
|
||||||
|
) AS family_counts
|
||||||
|
ON family_counts.user_id = pwi.user_id
|
||||||
|
WHERE pwi.account_type = :account_type
|
||||||
|
AND pwi.unionid = :unionid
|
||||||
|
AND p.status = 1
|
||||||
|
ORDER BY
|
||||||
|
(
|
||||||
|
COALESCE(binding_counts.cnt, 0)
|
||||||
|
+ COALESCE(relation_counts.cnt, 0)
|
||||||
|
+ COALESCE(family_counts.cnt, 0)
|
||||||
|
) DESC,
|
||||||
|
pwi.user_id ASC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
{"account_type": account_type, "unionid": unionid},
|
||||||
|
)
|
||||||
|
).mappings().first()
|
||||||
|
|
||||||
|
async def get_wechat_account_by_unionid(self, unionid: str) -> Optional[Mapping]:
|
||||||
|
return (
|
||||||
|
await self.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
pwa.*,
|
||||||
|
COALESCE(binding_counts.cnt, 0) AS active_binding_count,
|
||||||
|
COALESCE(relation_counts.cnt, 0) AS active_relation_count,
|
||||||
|
COALESCE(family_counts.cnt, 0) AS active_family_count
|
||||||
|
FROM parent_wechat_accounts AS pwa
|
||||||
|
JOIN parents AS p
|
||||||
|
ON p.user_id = pwa.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT owner_user_id AS user_id, COUNT(*) AS cnt
|
||||||
|
FROM device_bindings
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY owner_user_id
|
||||||
|
) AS binding_counts
|
||||||
|
ON binding_counts.user_id = pwa.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT user_id, COUNT(*) AS cnt
|
||||||
|
FROM parent_child_relations
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY user_id
|
||||||
|
) AS relation_counts
|
||||||
|
ON relation_counts.user_id = pwa.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT user_id, COUNT(*) AS cnt
|
||||||
|
FROM device_family_members
|
||||||
|
WHERE status = 1
|
||||||
|
GROUP BY user_id
|
||||||
|
) AS family_counts
|
||||||
|
ON family_counts.user_id = pwa.user_id
|
||||||
|
WHERE pwa.unionid = :unionid
|
||||||
|
AND p.status = 1
|
||||||
|
ORDER BY
|
||||||
|
(
|
||||||
|
COALESCE(binding_counts.cnt, 0)
|
||||||
|
+ COALESCE(relation_counts.cnt, 0)
|
||||||
|
+ COALESCE(family_counts.cnt, 0)
|
||||||
|
) DESC,
|
||||||
|
pwa.user_id ASC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
{"unionid": unionid},
|
||||||
|
)
|
||||||
|
).mappings().first()
|
||||||
|
|
||||||
|
async def upsert_wechat_identity(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
user_id: int,
|
||||||
|
app_id: str,
|
||||||
|
account_type: str,
|
||||||
|
openid: str,
|
||||||
|
unionid: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
await self.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO parent_wechat_identities (
|
||||||
|
user_id,
|
||||||
|
app_id,
|
||||||
|
account_type,
|
||||||
|
openid,
|
||||||
|
unionid,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
:user_id,
|
||||||
|
:app_id,
|
||||||
|
:account_type,
|
||||||
|
:openid,
|
||||||
|
:unionid,
|
||||||
|
CURRENT_TIMESTAMP,
|
||||||
|
CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
user_id = VALUES(user_id),
|
||||||
|
openid = VALUES(openid),
|
||||||
|
unionid = COALESCE(VALUES(unionid), unionid),
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
""",
|
||||||
|
{
|
||||||
|
"user_id": user_id,
|
||||||
|
"app_id": app_id,
|
||||||
|
"account_type": account_type,
|
||||||
|
"openid": openid,
|
||||||
|
"unionid": unionid,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await self.commit()
|
||||||
|
|
||||||
async def update(
|
async def update(
|
||||||
self,
|
self,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
@@ -107,19 +323,111 @@ class ParentDAO(BaseDAO):
|
|||||||
unionid: Optional[str] = None,
|
unionid: Optional[str] = None,
|
||||||
nickname: Optional[str] = None,
|
nickname: Optional[str] = None,
|
||||||
avatar_url: Optional[str] = None,
|
avatar_url: Optional[str] = None,
|
||||||
|
app_id: Optional[str] = None,
|
||||||
|
account_type: str = "mini_program",
|
||||||
) -> int:
|
) -> int:
|
||||||
|
normalized_app_id = (app_id or "").strip()
|
||||||
|
normalized_account_type = (account_type or "mini_program").strip() or "mini_program"
|
||||||
|
if normalized_app_id:
|
||||||
|
existing_identity = await self.get_wechat_identity_by_openid(
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
)
|
||||||
|
if existing_identity:
|
||||||
|
user_id = int(existing_identity["user_id"])
|
||||||
|
await self.update_from_wechat_login(user_id, unionid, nickname, avatar_url)
|
||||||
|
await self.upsert_wechat_identity(
|
||||||
|
user_id=user_id,
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
if unionid:
|
||||||
|
union_identity = await self.get_wechat_identity_by_unionid(
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
if union_identity:
|
||||||
|
user_id = int(union_identity["user_id"])
|
||||||
|
await self.update_from_wechat_login(user_id, unionid, nickname, avatar_url)
|
||||||
|
await self.upsert_wechat_identity(
|
||||||
|
user_id=user_id,
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
union_account = await self.get_wechat_account_by_unionid(unionid)
|
||||||
|
if union_account:
|
||||||
|
user_id = int(union_account["user_id"])
|
||||||
|
await self.update_from_wechat_login(user_id, unionid, nickname, avatar_url)
|
||||||
|
await self.upsert_wechat_identity(
|
||||||
|
user_id=user_id,
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
union_parent = await self.get_by_unionid(unionid)
|
||||||
|
if union_parent:
|
||||||
|
user_id = int(union_parent["user_id"])
|
||||||
|
await self.update_from_wechat_login(user_id, unionid, nickname, avatar_url)
|
||||||
|
await self.upsert_wechat_identity(
|
||||||
|
user_id=user_id,
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|
||||||
existing = await self.get_by_openid(openid)
|
existing = await self.get_by_openid(openid)
|
||||||
if existing:
|
if existing:
|
||||||
await self.update_from_wechat_login(existing["user_id"], unionid, nickname, avatar_url)
|
user_id = int(existing["user_id"])
|
||||||
return int(existing["user_id"])
|
await self.update_from_wechat_login(user_id, unionid, nickname, avatar_url)
|
||||||
|
if normalized_app_id:
|
||||||
|
await self.upsert_wechat_identity(
|
||||||
|
user_id=user_id,
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await self.create(openid, unionid, nickname, avatar_url)
|
user_id = await self.create(openid, unionid, nickname, avatar_url)
|
||||||
|
if normalized_app_id:
|
||||||
|
await self.upsert_wechat_identity(
|
||||||
|
user_id=user_id,
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
await self.db.rollback()
|
await self.db.rollback()
|
||||||
existing = await self.get_by_openid(openid)
|
existing = await self.get_by_openid(openid)
|
||||||
if not existing:
|
if not existing:
|
||||||
raise
|
raise
|
||||||
|
user_id = int(existing["user_id"])
|
||||||
if unionid or nickname or avatar_url:
|
if unionid or nickname or avatar_url:
|
||||||
await self.update_from_wechat_login(existing["user_id"], unionid, nickname, avatar_url)
|
await self.update_from_wechat_login(user_id, unionid, nickname, avatar_url)
|
||||||
return int(existing["user_id"])
|
if normalized_app_id:
|
||||||
|
await self.upsert_wechat_identity(
|
||||||
|
user_id=user_id,
|
||||||
|
app_id=normalized_app_id,
|
||||||
|
account_type=normalized_account_type,
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
)
|
||||||
|
return user_id
|
||||||
|
|||||||
@@ -19,11 +19,20 @@ class ParentService(DatabaseServiceBase):
|
|||||||
unionid: Optional[str] = None,
|
unionid: Optional[str] = None,
|
||||||
nickname: Optional[str] = None,
|
nickname: Optional[str] = None,
|
||||||
avatar_url: Optional[str] = None,
|
avatar_url: Optional[str] = None,
|
||||||
|
app_id: Optional[str] = None,
|
||||||
|
account_type: str = "mini_program",
|
||||||
) -> Mapping:
|
) -> Mapping:
|
||||||
db_session = await self.get_session()
|
db_session = await self.get_session()
|
||||||
try:
|
try:
|
||||||
dao = ParentDAO(db_session)
|
dao = ParentDAO(db_session)
|
||||||
user_id = await dao.upsert(openid, unionid, nickname, avatar_url)
|
user_id = await dao.upsert(
|
||||||
|
openid,
|
||||||
|
unionid,
|
||||||
|
nickname,
|
||||||
|
avatar_url,
|
||||||
|
app_id=app_id or settings.wechat_app_id,
|
||||||
|
account_type=account_type,
|
||||||
|
)
|
||||||
return await self.get(user_id)
|
return await self.get(user_id)
|
||||||
finally:
|
finally:
|
||||||
await db_session.close()
|
await db_session.close()
|
||||||
|
|||||||
@@ -381,6 +381,32 @@ async def _ensure_parent_wechat_accounts_table(conn) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _ensure_parent_wechat_identities_table(conn) -> None:
|
||||||
|
await conn.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS parent_wechat_identities (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
app_id VARCHAR(64) NOT NULL,
|
||||||
|
account_type VARCHAR(32) NOT NULL,
|
||||||
|
openid VARCHAR(64) NOT NULL,
|
||||||
|
unionid VARCHAR(64) NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_parent_wechat_identity_app_openid (app_id, account_type, openid),
|
||||||
|
KEY idx_parent_wechat_identity_user (user_id),
|
||||||
|
KEY idx_parent_wechat_identity_unionid (unionid),
|
||||||
|
CONSTRAINT fk_parent_wechat_identity_user
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES parents (user_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _ensure_wechat_mp_bind_states_table(conn) -> None:
|
async def _ensure_wechat_mp_bind_states_table(conn) -> None:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
text(
|
text(
|
||||||
@@ -428,6 +454,7 @@ async def init_db():
|
|||||||
await _ensure_cards_allow_multiple_per_device(conn)
|
await _ensure_cards_allow_multiple_per_device(conn)
|
||||||
await _ensure_device_family_tables(conn)
|
await _ensure_device_family_tables(conn)
|
||||||
await _ensure_parent_wechat_accounts_table(conn)
|
await _ensure_parent_wechat_accounts_table(conn)
|
||||||
|
await _ensure_parent_wechat_identities_table(conn)
|
||||||
await _ensure_wechat_mp_bind_states_table(conn)
|
await _ensure_wechat_mp_bind_states_table(conn)
|
||||||
await engine.dispose()
|
await engine.dispose()
|
||||||
session_logger.info("system", "database", "数据库表已成功创建")
|
session_logger.info("system", "database", "数据库表已成功创建")
|
||||||
|
|||||||
@@ -245,6 +245,25 @@ class ParentWechatAccount(Base):
|
|||||||
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||||
|
|
||||||
|
|
||||||
|
class ParentWechatIdentity(Base):
|
||||||
|
__tablename__ = "parent_wechat_identities"
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint("app_id", "account_type", "openid", name="uq_parent_wechat_identity_app_openid"),
|
||||||
|
Index("idx_parent_wechat_identity_user", "user_id"),
|
||||||
|
Index("idx_parent_wechat_identity_unionid", "unionid"),
|
||||||
|
{'mysql_charset': 'utf8mb4', 'mysql_collate': 'utf8mb4_unicode_ci'}
|
||||||
|
)
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||||
|
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("parents.user_id"), nullable=False)
|
||||||
|
app_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||||
|
account_type: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||||
|
openid: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||||
|
unionid: Mapped[Optional[str]] = mapped_column(String(64))
|
||||||
|
created_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||||
|
updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||||
|
|
||||||
|
|
||||||
class WechatMpBindState(Base):
|
class WechatMpBindState(Base):
|
||||||
__tablename__ = "wechat_mp_bind_states"
|
__tablename__ = "wechat_mp_bind_states"
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
|
|||||||
@@ -256,6 +256,24 @@ CREATE TABLE IF NOT EXISTS `parents` (
|
|||||||
KEY `idx_parents_phone` (`phone`)
|
KEY `idx_parents_phone` (`phone`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `parent_wechat_identities` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` BIGINT NOT NULL,
|
||||||
|
`app_id` VARCHAR(64) NOT NULL,
|
||||||
|
`account_type` VARCHAR(32) NOT NULL,
|
||||||
|
`openid` VARCHAR(64) NOT NULL,
|
||||||
|
`unionid` VARCHAR(64) NULL,
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uq_parent_wechat_identity_app_openid` (`app_id`, `account_type`, `openid`),
|
||||||
|
KEY `idx_parent_wechat_identity_user` (`user_id`),
|
||||||
|
KEY `idx_parent_wechat_identity_unionid` (`unionid`),
|
||||||
|
CONSTRAINT `fk_parent_wechat_identity_user`
|
||||||
|
FOREIGN KEY (`user_id`)
|
||||||
|
REFERENCES `parents` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `children` (
|
CREATE TABLE IF NOT EXISTS `children` (
|
||||||
`child_id` BIGINT NOT NULL AUTO_INCREMENT,
|
`child_id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
`child_name` VARCHAR(32) NOT NULL,
|
`child_name` VARCHAR(32) NOT NULL,
|
||||||
|
|||||||
310
talkingq-url/tests/test_parent_wechat_identity.py
Normal file
310
talkingq-url/tests/test_parent_wechat_identity.py
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from banban.dao.parent import ParentDAO
|
||||||
|
from banban.service.parent import ParentService
|
||||||
|
from database.init_db import _ensure_parent_wechat_identities_table
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSession:
|
||||||
|
async def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FakeScalarResult:
|
||||||
|
def scalar(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class FakeConnection:
|
||||||
|
def __init__(self):
|
||||||
|
self.sql = []
|
||||||
|
|
||||||
|
async def execute(self, statement, params=None):
|
||||||
|
del params
|
||||||
|
self.sql.append(str(statement))
|
||||||
|
return FakeScalarResult()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ensure_parent_wechat_identities_table_creates_multi_app_mapping_table():
|
||||||
|
conn = FakeConnection()
|
||||||
|
|
||||||
|
await _ensure_parent_wechat_identities_table(conn)
|
||||||
|
|
||||||
|
executed = "\n".join(conn.sql)
|
||||||
|
assert "CREATE TABLE IF NOT EXISTS parent_wechat_identities" in executed
|
||||||
|
assert "UNIQUE KEY uq_parent_wechat_identity_app_openid (app_id, account_type, openid)" in executed
|
||||||
|
assert "KEY idx_parent_wechat_identity_unionid (unionid)" in executed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parent_service_passes_current_wechat_app_id_to_identity_upsert(monkeypatch):
|
||||||
|
service = ParentService()
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
async def fake_get_session():
|
||||||
|
return FakeSession()
|
||||||
|
|
||||||
|
async def fake_upsert(self, openid, unionid=None, nickname=None, avatar_url=None, app_id=None, account_type="mini_program"):
|
||||||
|
calls.append(
|
||||||
|
{
|
||||||
|
"openid": openid,
|
||||||
|
"unionid": unionid,
|
||||||
|
"nickname": nickname,
|
||||||
|
"avatar_url": avatar_url,
|
||||||
|
"app_id": app_id,
|
||||||
|
"account_type": account_type,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return 42
|
||||||
|
|
||||||
|
async def fake_get(user_id):
|
||||||
|
return {
|
||||||
|
"user_id": user_id,
|
||||||
|
"openid": "openid-new",
|
||||||
|
"unionid": "union-1",
|
||||||
|
"nickname": "家长",
|
||||||
|
"avatar_url": None,
|
||||||
|
"phone": None,
|
||||||
|
"status": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(service, "get_session", fake_get_session)
|
||||||
|
monkeypatch.setattr("banban.service.parent.settings.wechat_app_id", "wx-new")
|
||||||
|
monkeypatch.setattr("banban.service.parent.ParentDAO.upsert", fake_upsert)
|
||||||
|
monkeypatch.setattr(service, "get", fake_get)
|
||||||
|
|
||||||
|
parent = await service.create(
|
||||||
|
openid="openid-new",
|
||||||
|
unionid="union-1",
|
||||||
|
nickname="家长",
|
||||||
|
avatar_url=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert parent["user_id"] == 42
|
||||||
|
assert calls == [
|
||||||
|
{
|
||||||
|
"openid": "openid-new",
|
||||||
|
"unionid": "union-1",
|
||||||
|
"nickname": "家长",
|
||||||
|
"avatar_url": None,
|
||||||
|
"app_id": "wx-new",
|
||||||
|
"account_type": "mini_program",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parent_dao_upsert_reuses_existing_identity(monkeypatch):
|
||||||
|
dao = ParentDAO(FakeSession())
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
async def fake_get_identity_by_openid(**kwargs):
|
||||||
|
calls.append(("identity_openid", kwargs))
|
||||||
|
return {"user_id": 7}
|
||||||
|
|
||||||
|
async def fake_update(user_id, unionid=None, nickname=None, avatar_url=None):
|
||||||
|
calls.append(("update", user_id, unionid, nickname, avatar_url))
|
||||||
|
|
||||||
|
async def fake_upsert_identity(**kwargs):
|
||||||
|
calls.append(("upsert_identity", kwargs))
|
||||||
|
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_identity_by_openid", fake_get_identity_by_openid)
|
||||||
|
monkeypatch.setattr(dao, "update_from_wechat_login", fake_update)
|
||||||
|
monkeypatch.setattr(dao, "upsert_wechat_identity", fake_upsert_identity)
|
||||||
|
|
||||||
|
user_id = await dao.upsert(
|
||||||
|
"openid-new",
|
||||||
|
"union-1",
|
||||||
|
"新昵称",
|
||||||
|
None,
|
||||||
|
app_id="wx-new",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user_id == 7
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
"identity_openid",
|
||||||
|
{
|
||||||
|
"app_id": "wx-new",
|
||||||
|
"account_type": "mini_program",
|
||||||
|
"openid": "openid-new",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
("update", 7, "union-1", "新昵称", None),
|
||||||
|
(
|
||||||
|
"upsert_identity",
|
||||||
|
{
|
||||||
|
"user_id": 7,
|
||||||
|
"app_id": "wx-new",
|
||||||
|
"account_type": "mini_program",
|
||||||
|
"openid": "openid-new",
|
||||||
|
"unionid": "union-1",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parent_dao_upsert_links_new_openid_to_existing_union_parent(monkeypatch):
|
||||||
|
dao = ParentDAO(FakeSession())
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
async def fake_get_identity_by_openid(**kwargs):
|
||||||
|
calls.append(("identity_openid", kwargs))
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_identity_by_unionid(**kwargs):
|
||||||
|
calls.append(("identity_unionid", kwargs))
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_wechat_account_by_unionid(unionid):
|
||||||
|
calls.append(("mp_account_unionid", unionid))
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_by_unionid(unionid):
|
||||||
|
calls.append(("parent_unionid", unionid))
|
||||||
|
return {"user_id": 4}
|
||||||
|
|
||||||
|
async def fake_update(user_id, unionid=None, nickname=None, avatar_url=None):
|
||||||
|
calls.append(("update", user_id, unionid, nickname, avatar_url))
|
||||||
|
|
||||||
|
async def fake_upsert_identity(**kwargs):
|
||||||
|
calls.append(("upsert_identity", kwargs))
|
||||||
|
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_identity_by_openid", fake_get_identity_by_openid)
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_identity_by_unionid", fake_get_identity_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_account_by_unionid", fake_get_wechat_account_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "get_by_unionid", fake_get_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "update_from_wechat_login", fake_update)
|
||||||
|
monkeypatch.setattr(dao, "upsert_wechat_identity", fake_upsert_identity)
|
||||||
|
|
||||||
|
user_id = await dao.upsert(
|
||||||
|
"openid-new",
|
||||||
|
"union-1",
|
||||||
|
"新昵称",
|
||||||
|
None,
|
||||||
|
app_id="wx-new",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user_id == 4
|
||||||
|
assert ("parent_unionid", "union-1") in calls
|
||||||
|
assert calls[-1] == (
|
||||||
|
"upsert_identity",
|
||||||
|
{
|
||||||
|
"user_id": 4,
|
||||||
|
"app_id": "wx-new",
|
||||||
|
"account_type": "mini_program",
|
||||||
|
"openid": "openid-new",
|
||||||
|
"unionid": "union-1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parent_dao_upsert_links_new_openid_to_existing_mp_union_account(monkeypatch):
|
||||||
|
dao = ParentDAO(FakeSession())
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
async def fake_get_identity_by_openid(**kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_identity_by_unionid(**kwargs):
|
||||||
|
calls.append(("identity_unionid", kwargs))
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_wechat_account_by_unionid(unionid):
|
||||||
|
calls.append(("mp_account_unionid", unionid))
|
||||||
|
return {"user_id": 4}
|
||||||
|
|
||||||
|
async def fake_update(user_id, unionid=None, nickname=None, avatar_url=None):
|
||||||
|
calls.append(("update", user_id, unionid, nickname, avatar_url))
|
||||||
|
|
||||||
|
async def fake_upsert_identity(**kwargs):
|
||||||
|
calls.append(("upsert_identity", kwargs))
|
||||||
|
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_identity_by_openid", fake_get_identity_by_openid)
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_identity_by_unionid", fake_get_identity_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_account_by_unionid", fake_get_wechat_account_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "update_from_wechat_login", fake_update)
|
||||||
|
monkeypatch.setattr(dao, "upsert_wechat_identity", fake_upsert_identity)
|
||||||
|
|
||||||
|
user_id = await dao.upsert(
|
||||||
|
"openid-new",
|
||||||
|
"union-1",
|
||||||
|
"新昵称",
|
||||||
|
None,
|
||||||
|
app_id="wx-new",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user_id == 4
|
||||||
|
assert ("mp_account_unionid", "union-1") in calls
|
||||||
|
assert calls[-1] == (
|
||||||
|
"upsert_identity",
|
||||||
|
{
|
||||||
|
"user_id": 4,
|
||||||
|
"app_id": "wx-new",
|
||||||
|
"account_type": "mini_program",
|
||||||
|
"openid": "openid-new",
|
||||||
|
"unionid": "union-1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parent_dao_upsert_creates_new_parent_and_identity_for_new_user(monkeypatch):
|
||||||
|
dao = ParentDAO(FakeSession())
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
async def fake_get_identity_by_openid(**kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_identity_by_unionid(**kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_by_unionid(unionid):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_wechat_account_by_unionid(unionid):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_get_by_openid(openid):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def fake_create(openid, unionid=None, nickname=None, avatar_url=None):
|
||||||
|
calls.append(("create", openid, unionid, nickname, avatar_url))
|
||||||
|
return 99
|
||||||
|
|
||||||
|
async def fake_upsert_identity(**kwargs):
|
||||||
|
calls.append(("upsert_identity", kwargs))
|
||||||
|
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_identity_by_openid", fake_get_identity_by_openid)
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_identity_by_unionid", fake_get_identity_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "get_by_unionid", fake_get_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "get_wechat_account_by_unionid", fake_get_wechat_account_by_unionid)
|
||||||
|
monkeypatch.setattr(dao, "get_by_openid", fake_get_by_openid)
|
||||||
|
monkeypatch.setattr(dao, "create", fake_create)
|
||||||
|
monkeypatch.setattr(dao, "upsert_wechat_identity", fake_upsert_identity)
|
||||||
|
|
||||||
|
user_id = await dao.upsert(
|
||||||
|
"openid-brand-new",
|
||||||
|
None,
|
||||||
|
"新用户",
|
||||||
|
None,
|
||||||
|
app_id="wx-new",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user_id == 99
|
||||||
|
assert calls == [
|
||||||
|
("create", "openid-brand-new", None, "新用户", None),
|
||||||
|
(
|
||||||
|
"upsert_identity",
|
||||||
|
{
|
||||||
|
"user_id": 99,
|
||||||
|
"app_id": "wx-new",
|
||||||
|
"account_type": "mini_program",
|
||||||
|
"openid": "openid-brand-new",
|
||||||
|
"unionid": None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user