3、第一个模块:用户账户系统(上)——设计用户类,包含用户名、密码、初始资金等属性

好,咱们正式开始动手写代码了。

第一个模块,我选的是用户账户系统。为什么先做这个?你想想看,一个交易模拟器,总得有人来操作吧?不管是买股票、卖基金,还是查看盈亏,所有动作都得挂在一个用户下面。没有用户,后面的交易逻辑就是空中楼阁。

说白了,用户账户系统是整个软件的“地基”。地基打不牢,后面盖多少层楼都悬。我在早期做项目时,就吃过这个亏——用户类设计得太随意,结果后面加功能时,改得我头皮发麻。所以这次,咱们从一开始就把事情做对。

3.1 用户类需要承载什么?

一个用户,在交易系统里,至少得有三个核心信息:

  • 用户名:用来标识身份,不能重复。
  • 密码:用来验证身份,保护账户安全。
  • 初始资金:用户开始交易时有多少钱,这是交易的“弹药”。

嗯,听起来很简单对吧?但实际项目中,这三个属性背后藏着不少细节。我一个个说。

3.2 用户名——别小看这个字段

用户名看起来就是个字符串,但我在项目中遇到过一个问题:用户注册时,有人用“admin”,有人用“Admin”,还有人用“ADMIN”。系统怎么处理?

我的习惯是:用户名统一转成小写存储。这样“Admin”和“admin”就是同一个人,避免重复注册的麻烦。

另外,用户名长度也要限制。太短容易被猜到,太长用户自己都记不住。我个人建议:3到20个字符,只允许字母、数字和下划线。

小技巧:在Python里,可以用 str.isalnum()'_' in username 来快速校验。但注意,isalnum() 会把下划线判为False,所以得单独处理。

3.3 密码——安全第一

密码这个事,我多说两句。很多新手喜欢直接把密码明文存到数据库里,这是大忌。我曾经接手过一个项目,用户密码全是明文,吓得我连夜加班改代码。

正确的做法是:永远不要存明文密码。存的是密码的哈希值。Python里有个内置库 hashlib,用起来很方便。

咱们这个课程虽然只是模拟器,但养成好习惯很重要。所以从一开始,我就把密码哈希处理写进去。

警告:即使是模拟器,也不要偷懒存明文密码。一旦你习惯了,真项目里很容易犯同样的错误。相信我,这个坑我踩过。

3.4 初始资金——浮点数还是整数?

资金用浮点数?还是用整数?这是个经典问题。

浮点数有精度问题。0.1 + 0.2 在计算机里不等于 0.3,这个你应该听说过。做金融系统,精度就是生命。差一分钱,用户都可能投诉你。

我的建议是:用整数存储,单位是“分”。比如100元,存成10000分。显示的时候再转成元。这样加减乘除都不会有精度问题。

当然,咱们这个模拟器初期,为了直观,我暂时用浮点数。但我会在代码里加注释,提醒你以后要改。

3.5 代码实现——第一个User类

好了,理论说完了,咱们直接上代码。这是咱们整个课程的第一段代码,我写得尽量清晰。

import hashlib

class User:
    """用户类,存储用户基本信息"""
    
    def __init__(self, username: str, password: str, initial_balance: float = 10000.0):
        # 用户名统一转小写,避免大小写问题
        self.username = username.lower().strip()
        
        # 密码绝不存明文,存哈希值
        self.password_hash = self._hash_password(password)
        
        # 初始资金,暂时用浮点数,后续可改为整数(单位:分)
        self.balance = initial_balance
        
        # 记录用户创建时间,方便后续审计
        self.created_at = None  # 后面章节会补充时间戳
        
    def _hash_password(self, password: str) -> str:
        """对密码进行SHA-256哈希处理"""
        # 加盐(salt)可以增加安全性,这里先简单处理
        return hashlib.sha256(password.encode('utf-8')).hexdigest()
    
    def check_password(self, password: str) -> bool:
        """验证密码是否正确"""
        return self.password_hash == hashlib.sha256(password.encode('utf-8')).hexdigest()
    
    def __str__(self) -> str:
        """友好的字符串表示"""
        return f"User({self.username}, 余额: {self.balance:.2f}元)"

这段代码里,我做了几件事:

  • 用户名转小写并去空格
  • 密码用SHA-256哈希存储
  • 提供了 check_password 方法,方便后续登录验证
  • 重写了 __str__,打印用户信息时更直观
重点_hash_password 方法名前加了下划线,表示这是内部方法,外部不应该直接调用。这是Python的约定,虽然不是强制的,但建议遵守。

3.6 测试一下

写完了类,咱们得跑一跑,看看效果。我习惯每写一段代码就测试,而不是攒一堆再调试。那样出了问题,找起来太痛苦。

# 创建一个用户
user1 = User("Alice", "my_secret_123")
print(user1)  # 输出: User(alice, 余额: 10000.00元)

# 验证密码
print(user1.check_password("my_secret_123"))  # True
print(user1.check_password("wrong_password"))  # False

# 查看密码哈希值(注意:这是内部属性,一般不应直接访问)
print(user1.password_hash)  
# 输出类似: a1b2c3d4...(一串64位的十六进制字符串)

看到没?密码验证成功了,但密码本身并没有被存储。这就是哈希的好处。

3.7 知识结构图

为了让你更直观地理解这个用户类的设计,我画了一张图。它展示了用户类的核心属性和方法,以及它们之间的关系。

用户类(User)结构图 User 类 属性(Attributes) • username : str • password_hash : str • balance : float • created_at : datetime (created_at 后续章节补充) 方法(Methods) • __init__() • _hash_password() • check_password() • __str__() 核心设计:用户名转小写 + 密码哈希存储 + 资金用浮点数(后续改为整数) ⚠ 密码绝不存明文,这是底线

3.8 避坑指南

最后,分享几个我踩过的坑:

  • 忘记处理用户名大小写:我曾经有个项目,用户“Tom”和“tom”被当成两个人,结果数据乱成一锅粥。
  • 密码哈希不加盐:虽然SHA-256比MD5安全,但如果不加盐,两个用户密码相同,哈希值也相同。这会给彩虹表攻击留下漏洞。
  • 初始资金用浮点数直接运算:如果你在后续代码里做 balance += 0.1 这样的操作,很快就会出现 0.30000000000000004 这种尴尬情况。
我的建议:现在就把 balance 改成整数(单位:分),虽然显示时麻烦一点,但能避免未来99%的精度问题。如果你现在不改,记得在代码里加个TODO注释。

好了,这一节的内容就到这。用户类的骨架已经搭好了,下一节咱们会往里面加更多功能,比如交易记录、持仓管理等等。但今天,先把地基夯实。


专注资料整理