☕ 第十五章 综合项目:客户关系管理系统

15.5 登录管理

📄 一句话概括

把 15.2 的临时工门卫转正——LoginManager 登录管理类。散装 login() 升级后多了三样本事:多账号管理(User[] 货架找人)、登录日志(谁、什么时候登的)、账号锁定(连错 3 次拉闸)。本节真正的收获是认识一个新角色:管理类——只管业务、不装数据,和实体类是平级同事,15.1 的分工口诀到此补全。


一、为什么门卫要"转正"

15.2 的门卫是什么样:login() 一个方法,账号密码写死("admin"/"123456"),只有一个账号,错了三次就三次,谁登过、啥时候登的——一概不记得。练习够用,项目不够格。

真项目里门卫要管一群人:账号得能加、密码得能改(15.4 已经铺了)、老输错的人得拦一拦、出了事得能查"谁几点登过"。这些活一个人干不完,就设个专门的管理岗——LoginManager。

实体类 vs 管理类,这是本节最重要的区分:

Customer / User(实体类) LoginManager(管理类)
装什么 一个对象的数据 一群对象的业务逻辑
字段 姓名、电话…… 账号数组、失败计数、日志
方法 getter/setter、show() 验证、记账、锁定
一句话 管数据 管业务

二、核心:LoginManager 三职责

import java.util.Scanner;

public class LoginManager {
    private User[] accounts;              // 账号货架(2.7 数组 + 15.4 User)
    private int userIndex;                // 货架已放几个
    private int failCount;                // 当前账号连错次数
    private boolean locked;               // 锁定开关
    private String lastLoginInfo;         // 登录日志(谁 + 何时)

    public LoginManager(int capacity) {
        accounts = new User[capacity];    // 货架容量定死,够了就翻倍(7 章集合的伏笔)
        failCount = 0;
        locked = false;
    }

    // 添加账号:管理类的"行政"活
    public void add(User u) {
        accounts[userIndex++] = u;
    }

    // 职责一 + 三:验证 + 锁定
    public boolean login(String name, String pwd) {
        if (locked) {                                          // 卡口在验证前面!
            System.out.println("账号已锁定,请联系管理员。");
            return false;
        }
        for (int i = 0; i < userIndex; i++) {
            User u = accounts[i];
            if (u.getUsername().equals(name)) {                // 6.2 equals:账号也用 equals
                if (u.getPassword().equals(pwd)) {             // 常量打头不可行时,确保 u 非 null
                    lastLoginInfo = u.getRealName() + " 登录于 " + System.currentTimeMillis();
                    failCount = 0;                             // 登录成功,前账清零
                    System.out.println("登录成功!" + lastLoginInfo);
                    return true;
                }
                failCount++;
                if (failCount >= 3) {
                    locked = true;                             // 连错 3 次,拉闸
                    System.out.println("连续错误 3 次,账号已锁定!");
                } else {
                    System.out.println("密码错误,还剩 " + (3 - failCount) + " 次机会");
                }
                return false;
            }
        }
        System.out.println("账号不存在。");
        return false;
    }
}

三处讲究:

  1. 锁定卡口放在验证前面——锁了的账号连"验证密码"的资格都没有,直接拒收。这就是"管理"的分量:不是只会对答案,还会维持秩序。
  2. 失败计数跟人走:本版计数器挂在管理器上(单账号场景);多账号各自计数时,failCount 应搬进 User 实体——数据跟实体走,逻辑跟管理走,这句是后面所有管理系统的心法。
  3. 登录成功先清零再放行:failCount = 0 忘了写,改对密码也进不去——计数器状态是管理类的命根。

三、Demo35:两个账号跑一遍

public class Demo35 {
    public static void main(String[] args) {
        LoginManager lm = new LoginManager(10);
        lm.add(new User("admin", "123456", "波仔", "13800000000", "[email protected]"));
        lm.add(new User("xiaohong", "abc123", "小红", "13800000002", "[email protected]"));

        Scanner sc = new Scanner(System.in);
        for (int i = 2; i >= 0; i--) {                 // 15.2 三次机会骨架照用
            System.out.print("用户名:");
            String name = sc.next();
            System.out.print("密码:");
            String pwd = sc.next();
            if (lm.login(name, pwd)) {
                return;                                 // 放行,进入主菜单
            }
        }
        System.out.println("机会用完,再见。");
    }
}

main 里只剩"收证 → 问管理 → 看结果"——验证细节全在 LoginManager 里,main 越来越薄,类越来越专业,这就是 15.2 说的"main 只看结果"进化到项目形态。

🧪 实验 1:连错三次看拉闸

用 admin 连续输错密码三次——看到"还剩 2 次→还剩 1 次→账号已锁定"。第四次输对了也进不去:卡口在验证前面,锁定不给面子。

🧪 实验 2:日志加上可读时间

把 System.currentTimeMillis() 换成 6.6 的 LocalDate.now():"登录于 " + LocalDate.now()——时间戳人看不懂,日期人爱看。两个老朋友各干一摊。

🧪 实验 3:把 failCount 搬进 User

给 User 加 private int failCount; 和对应 get/set,管理器里改成 u.setFailCount(u.getFailCount() + 1)——两个账号互不干扰,A 错满 3 次不影响 B。体会"数据跟实体走"。

四、五个常见误区

⚠️ 误区 1:锁定卡口放在密码验证后面

先验密码再查锁定——锁了的账号输对密码照样进,锁定形同虚设。秩序检查永远排在业务验证前面。

⚠️ 误区 2:登录成功忘了清零失败计数

第一次错两次、第二次对——计数器还挂着 2,下次错一次就误锁。成功放行前 failCount = 0 是铁律。

⚠️ 误区 3:日志里记密码

lastLoginInfo = name + " / " + pwd——日志一旦流出,密码全泄。日志记"谁、何时、成败",永远不记密码(15.4 打码规矩的延续)。

⚠️ 误区 4:管理类里塞一堆业务无关字段

比如把"客户信息"也塞进 LoginManager——管理类只管登录这一摊。一摊一个类,15.1 分工口诀不破。

⚠️ 误区 5:数组满了不报错

add() 第 11 个账号时 accounts[10] 直接 ArrayIndexOutOfBoundsException(第五章的老朋友)。防御姿势:add 前判断 userIndex >= accounts.length,要么报错要么扩容——扩容就是 7 章集合登场前埋的最后一颗钩子。

五、自测题

❓ Q1:管理类和实体类的区别是什么?

实体类(Customer/User)装一个对象的数据,方法以 getter/setter/show 为主;管理类(LoginManager)装一群对象的业务逻辑,字段是数组/计数器/日志,方法是验证/记账/锁定。数据跟实体走,逻辑跟管理走。

❓ Q2:锁定判断为什么要放在密码验证前面?

锁定的含义是"此账号暂时失去验证资格"——卡口在后,锁定的人输对密码照样进,秩序就没了。

❓ Q3:多账号时失败计数应该放哪?为什么?

搬进 User 实体——计数是"这个账号"的数据,数据跟实体走;管理器只负责读写它。否则 A 的错误算到 B 头上。

❓ Q4:登录日志应该记哪些内容?绝不能记什么?

谁(用户名/姓名)、何时(时间戳或日期)、成败。绝不记密码——日志是最容易泄露的地方。

❓ Q5:(毕业题)add 账号时货架满了怎么办?写出防御代码。

if (userIndex >= accounts.length) { System.out.println("货架已满"); return; } 直接拒收;进阶做法是 new 一个双倍大数组、arraycopy(6.3 老朋友)搬过去再放——这正是 ArrayList 内部干的事,7.1 见。

六、下一步学什么


相关笔记