From 82914b355b989d699df1d26d7d2039cb45b1b9ea Mon Sep 17 00:00:00 2001 From: xiao12feng8 <16507319+xiao12feng8@user.noreply.gitee.com> Date: Sun, 1 Feb 2026 13:59:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9A=E4=BA=8C=E7=BB=B4?= =?UTF-8?q?=E7=A0=81=E6=8E=A8=E5=B9=BF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lover/__pycache__/models.cpython-314.pyc | Bin 24064 -> 24307 bytes lover/migrations/add_invite_code_fields.sql | 18 + lover/models.py | 5 + .../__pycache__/config.cpython-314.pyc | Bin 24848 -> 31740 bytes lover/routers/config.py | 141 +++ xuniYou/pages/login/index.vue | 96 +- xuniYou/pages/mine/invite.vue | 434 ++++---- xunifriend_RaeeC/runtime/log/202602/01.log | 955 ++++++++++++++++++ 开发/2026年2月1日.md | 3 +- 9 files changed, 1399 insertions(+), 253 deletions(-) create mode 100644 lover/migrations/add_invite_code_fields.sql diff --git a/lover/__pycache__/models.cpython-314.pyc b/lover/__pycache__/models.cpython-314.pyc index 712cc9c6abc15dc2f8f9fcab54f1e2ea5e75e9e7..91441474eaa18f326b97e481f9f479e0d256ae27 100644 GIT binary patch delta 4929 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 GAO0T#dfh4j diff --git a/lover/migrations/add_invite_code_fields.sql b/lover/migrations/add_invite_code_fields.sql new file mode 100644 index 0000000..cb94fcb --- /dev/null +++ b/lover/migrations/add_invite_code_fields.sql @@ -0,0 +1,18 @@ +-- 添加邀请码相关字段 +ALTER TABLE nf_user +ADD COLUMN invite_code VARCHAR(10) UNIQUE COMMENT '我的邀请码', +ADD COLUMN invited_by VARCHAR(10) COMMENT '被谁邀请(邀请码)', +ADD COLUMN invite_count INT DEFAULT 0 COMMENT '邀请人数', +ADD COLUMN invite_reward_total DECIMAL(10,2) DEFAULT 0.00 COMMENT '邀请奖励总额'; + +-- 为已有用户生成邀请码(6位随机字符) +UPDATE nf_user +SET invite_code = CONCAT( + SUBSTRING('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', FLOOR(1 + RAND() * 32), 1), + SUBSTRING('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', FLOOR(1 + RAND() * 32), 1), + SUBSTRING('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', FLOOR(1 + RAND() * 32), 1), + SUBSTRING('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', FLOOR(1 + RAND() * 32), 1), + SUBSTRING('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', FLOOR(1 + RAND() * 32), 1), + SUBSTRING('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', FLOOR(1 + RAND() * 32), 1) +) +WHERE invite_code IS NULL; diff --git a/lover/models.py b/lover/models.py index e7b0111..28b1f06 100644 --- a/lover/models.py +++ b/lover/models.py @@ -31,6 +31,11 @@ class User(Base): owned_outfit_ids = Column(JSON) owned_voice_ids = Column(JSON) vip_endtime = Column(BigInteger, default=0) + # 邀请码相关 + invite_code = Column(String(10), unique=True) + invited_by = Column(String(10)) + invite_count = Column(Integer, default=0) + invite_reward_total = Column(DECIMAL(10, 2), default=0.00) class VoiceLibrary(Base): diff --git a/lover/routers/__pycache__/config.cpython-314.pyc b/lover/routers/__pycache__/config.cpython-314.pyc index 69aea364c430f06f669db4c923e4746c85b1f0cc..e494acc5a20eb925ea9a13b23b8129e26d1a8c22 100644 GIT binary patch delta 7492 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 AyZ`_I delta 2882 zcma)8dr*{B6!-2f%fcd}A})x_f}nl~>NJj^_`;O83-S;j49{I{ab4){5;~4DV`G+v zqh2OW&9@~^n)YR6P0466I#Z2Pqt$0-nGnwOPc}~fI8FC=7FQnX#2-KIJ@=k_&iS3k zw~zi|yDqcvDdEFIhsnRDdz)QZvv-GEbVm&5Cm%8xi}e$RDVgf^pcWHTL{a!$HcQMJRa+%;z<4&aVf?qOi>l_B{&J;r8p@AECZASY5=vOC1>oY{GR>{ffhPlOc;( zashb&Ghq1;f1D6IMN#w|=0xn`-RQ4awQ$EIMbnW@o@+s?-3>Lqfz~g@R9ax&-X1)} z{17WHSaFHUana1xXJw=~Hf|5|)0r2WF@xUX4Y*h#p+9FFLI8Qxh+QIy0#E%OdwjAF zA~#^IgvZ>igFp|#fCOF{U;aFc&Cmjn<4-I$ON+fPWaG3NP%A+R@+SyJ{H64aqS+Kz z8%y~z4Izx)8p7H}h&PHv+eEfeDUvM^yb9P3sFt7&cQ{wqG`W3MuQa&ooU)G3 zB_*>q^;XheW)9qM*NI*A6V>CBt{T`@;ZCu{ZIikWM@aai?Nra}9rr$I?LM=w``{@h zn7l$|xAavu%uuE%XG)CUICS|Az-|CS;ky77MFdte07x;X+|I`;96-U~b^a!x6|fUP z5mP2*0WBQHvmhMQ9D zg5#pKUQyZfn@00iXul2EChpE$#`cR@X~iLm(I3`4(Uz7dJKz=NCJUS=l1G;?yN`ZX4tJMU9jFfY&4_=Ev!eqanOvRrm#A-zH-Hq9S%dy|(C>js|$G_%_=T3IEy|O@x6HXGBg(LWw-Iw?F-R zVTL0^=W_?d9pX$*GV74vi%KywH-^mBO@ObO(d(=6HF>q*bxyChW{s17CZz){|Cw9b zH|svfV}qw_Cxn1FK2NQA^?jeGl<`xowRc&(oIjN&xNjmN zla*8o?Li;HtB^e-x-Oh`iMoR6tV^^NxJ-xej$Q_gPMW$TUdN7#qQVrmO>8PmXBWlC zg)7Z|6-oBLD=mqbolRj9y0`AQzyG!Sht=LUKIGp?ft`ONF{@Mwm1928=fb_p z(h?x!-lcc&LFgkx8U2I@en%?%b4TsVuWS{eIY2(G~C+p01iN@x5_MX_&oX56^+s$iR zXrR)h&4hX-zzLwQ4gGGRpAs}%D8e?J9+aRN8XLSmeir)+0P6HktSHqtvAQckGdNsz vKHh_U34ppY0ViWjbJjIDn%vHL{BOCJ{X5L-(WwvD*Xzs=bQ!FW(dGXDeAK6P diff --git a/lover/routers/config.py b/lover/routers/config.py index 1382e25..7e568f7 100644 --- a/lover/routers/config.py +++ b/lover/routers/config.py @@ -492,3 +492,144 @@ def save_cloned_voice( except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"保存失败: {str(e)}") + + + +# ===== 邀请码相关 ===== + +class InviteInfoResponse(BaseModel): + invite_code: str + invite_count: int + invite_reward_total: float + invite_url: str + + model_config = ConfigDict(from_attributes=True) + + +class InviteRewardRequest(BaseModel): + invite_code: str + + model_config = ConfigDict(from_attributes=True) + + +def _generate_invite_code() -> str: + """生成6位邀请码(不含易混淆字符)""" + import random + import string + chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' # 去掉 0O1I 等易混淆字符 + return ''.join(random.choice(chars) for _ in range(6)) + + +@router.get("/invite/info", response_model=ApiResponse[InviteInfoResponse]) +def get_invite_info( + db: Session = Depends(get_db), + user: AuthedUser = Depends(get_current_user), +): + """ + 获取用户的邀请信息 + """ + user_row = db.query(User).filter(User.id == user.id).first() + if not user_row: + raise HTTPException(status_code=404, detail="用户不存在") + + # 如果没有邀请码,生成一个 + if not user_row.invite_code: + while True: + code = _generate_invite_code() + # 检查是否重复 + existing = db.query(User).filter(User.invite_code == code).first() + if not existing: + user_row.invite_code = code + db.add(user_row) + db.flush() + break + + # 生成邀请链接(这里使用简单的格式,实际可以是 H5 页面链接) + invite_url = f"https://your-domain.com/register?invite={user_row.invite_code}" + + return success_response( + InviteInfoResponse( + invite_code=user_row.invite_code, + invite_count=user_row.invite_count or 0, + invite_reward_total=float(user_row.invite_reward_total or 0), + invite_url=invite_url + ) + ) + + +@router.post("/invite/apply", response_model=ApiResponse[dict]) +def apply_invite_code( + payload: InviteRewardRequest, + db: Session = Depends(get_db), + user: AuthedUser = Depends(get_current_user), +): + """ + 新用户使用邀请码(注册后调用) + """ + user_row = db.query(User).filter(User.id == user.id).with_for_update().first() + if not user_row: + raise HTTPException(status_code=404, detail="用户不存在") + + # 检查是否已经使用过邀请码 + if user_row.invited_by: + raise HTTPException(status_code=400, detail="您已经使用过邀请码") + + # 不能使用自己的邀请码 + if user_row.invite_code == payload.invite_code: + raise HTTPException(status_code=400, detail="不能使用自己的邀请码") + + # 查找邀请人 + inviter = db.query(User).filter(User.invite_code == payload.invite_code).with_for_update().first() + if not inviter: + raise HTTPException(status_code=404, detail="邀请码不存在") + + # 奖励金额配置 + INVITER_REWARD = 10.00 # 邀请人获得10金币 + INVITEE_REWARD = 5.00 # 被邀请人获得5金币 + + # 给邀请人发放奖励 + inviter.money = float(inviter.money or 0) + INVITER_REWARD + inviter.invite_count = (inviter.invite_count or 0) + 1 + inviter.invite_reward_total = float(inviter.invite_reward_total or 0) + INVITER_REWARD + db.add(inviter) + + # 记录邀请人的金币日志 + db.add( + UserMoneyLog( + user_id=inviter.id, + money=Decimal(str(INVITER_REWARD)), + before=Decimal(str(float(inviter.money) - INVITER_REWARD)), + after=Decimal(str(inviter.money)), + memo=f"邀请新用户奖励", + createtime=int(time.time()), + ) + ) + + # 给被邀请人发放奖励 + user_row.money = float(user_row.money or 0) + INVITEE_REWARD + user_row.invited_by = payload.invite_code + db.add(user_row) + + # 记录被邀请人的金币日志 + db.add( + UserMoneyLog( + user_id=user.id, + money=Decimal(str(INVITEE_REWARD)), + before=Decimal(str(float(user_row.money) - INVITEE_REWARD)), + after=Decimal(str(user_row.money)), + memo=f"使用邀请码奖励", + createtime=int(time.time()), + ) + ) + + try: + db.flush() + except IntegrityError: + db.rollback() + raise HTTPException(status_code=409, detail="操作失败,请重试") + + return success_response({ + "message": f"邀请码使用成功!您获得了{INVITEE_REWARD}金币", + "reward": INVITEE_REWARD, + "balance": float(user_row.money) + }) diff --git a/xuniYou/pages/login/index.vue b/xuniYou/pages/login/index.vue index efb8ef0..08825dd 100644 --- a/xuniYou/pages/login/index.vue +++ b/xuniYou/pages/login/index.vue @@ -32,6 +32,19 @@ 🔧 开发模式:只需输入手机号即可登录 + + + + + + + + + ✅ 使用邀请码可获得5金币奖励 + + + + + 扫描二维码获取邀请码 + + + 长按保存二维码分享给好友 + + + + 邀请规则 + • 分享邀请码或二维码给好友 + • 好友扫码或输入邀请码注册 + • 您获得10金币,好友获得5金币 + • 金币可用于购买会员、音色等