首页
壁纸
统计
友链
Search
1
Vue2详细笔记
68 阅读
2
ExpressAPI
34 阅读
3
微信小程序
31 阅读
4
Spring MVC 请求生命周期
30 阅读
5
Nestjs概述-中文
29 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
40
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
40
篇与
的结果
2026-04-03
多线程编程笔记
Java 多线程编程笔记一、多线程概述1.1 什么是多线程Multi‑threading:一个程序内同时运行多个线程,并发执行不同任务。线程:CPU 调度的最小单位。比进程更轻量(创建/切换开销小)。1.2 多线程的优势提高持续响应性(UI 不卡顿)充分利用多核 CPU改善用户体验提高系统吞吐量1.3 多线程面临的问题问题说明线程安全数据竞争、共享资源并发访问、原子性被破坏死锁线程相互等待资源,程序永久阻塞内存一致性缓存、指令重排、可见性问题调试困难并发 Bug 不可复现、时序依赖、执行路径复杂二、Java 多线程模型JVM 线程模型:线程映射(通常 1:1 映射到 OS 线程)线程调度:由操作系统调度器决定(抢占式 + 时间片轮转)Java 内存模型(JMM):定义变量的可见性、有序性、原子性规则三、线程创建方式(4 种)方式实现特点继承 Thread 类class MyThread extends Thread { run() }简单,但受单继承限制实现 Runnableclass MyRunnable implements Runnable { run() }避免单继承,可共享任务实例实现 Callableclass MyCallable implements Callable<T> { call() }有返回值、可抛异常线程池Executors.newFixedThreadPool(n)生产首选,资源复用示例(Lambda 简化):new Thread(() -> System.out.println("run")).start();3.1 Callable + Future 工作流程(“点菜模型”)你点菜 → 提交 Callable 任务厨师做菜 → 线程池异步执行服务员给你叫号器 → 返回 Future 对象你玩手机 → 主线程不阻塞想吃饭了,去取餐 → 调用 future.get()没做好就等(阻塞),做好了直接拿走Future<Integer> future = executor.submit(() -> 100); Integer result = future.get(); // 阻塞直到结果返回四、线程基础操作 & 常用方法4.1 核心方法清单方法作用状态变化start()启动线程NEW → RUNNABLEThread.sleep(millis)休眠RUNNABLE → TIMED_WAITINGjoin()等待该线程结束RUNNABLE → WAITINGjoin(timeout)等待超时RUNNABLE → TIMED_WAITINGyield()让出 CPU(仍在就绪队列)RUNNABLE(不改变状态)Thread.currentThread()获取当前线程-setName() / getName()设置/获取线程名-setDaemon(true)设为守护线程必须在 start() 前调用4.2 线程属性优先级:setPriority(1~10),仅建议,不保证顺序。守护线程:JVM 退出时自动终止(如 GC 线程)。4.3 异常处理(重点)普通 try-catch 抓不住子线程里的异常。使用 UncaughtExceptionHandler:Thread thread = new Thread(() -> { throw new RuntimeException("崩了"); }); thread.setUncaughtExceptionHandler((t, e) -> { System.out.println("线程 " + t.getName() + " 异常:" + e.getMessage()); }); thread.start();五、线程生命周期(完整 6 种状态)NEW → RUNNABLE → BLOCKED / WAITING / TIMED_WAITING → TERMINATED状态触发条件NEWnew Thread(),尚未 start()RUNNABLEstart() 后,等待 CPU 调度BLOCKED进入 synchronized 但锁被占用WAITINGwait()、join() 无超时、LockSupport.park()TIMED_WAITINGsleep(time)、wait(time)、join(time)TERMINATEDrun() 执行完毕或异常退出六、线程调度机制抢占式调度:高优先级可抢占低优先级(但不保证)。时间片轮转:同优先级线程轮流获得时间片。公平调度:JVM 尽量避免线程饥饿。七、线程监控工具工具用途jstack命令行生成线程堆栈快照(定位死锁)jconsole图形化监控(线程、内存、类加载)VisualVM功能最强大的可视化工具八、线程间协作(wait / notify / notifyAll)必须在 synchronized 块内调用,否则 IllegalMonitorStateException。wait():释放锁,进入 WAITING,等待 notify/notifyAll。notify():随机唤醒一个等待线程。notifyAll():唤醒所有等待线程。synchronized (lock) { while (条件不满足) lock.wait(); // 条件满足,执行操作 lock.notifyAll(); }九、同步机制详解(从简单到复杂)9.1 synchronized(内置锁,无脑首选)自动加锁/解锁,不会忘记释放。锁某个对象 → 最小粒度,性能最好。锁当前对象 this → 整个方法串行。锁 Class 对象 → 全局锁。// 同步代码块(推荐) private final Object lock = new Object(); public void deduct() { synchronized(lock) { /* 临界区 */ } } // 同步方法 public synchronized void add() { count++; } // 静态同步方法(类锁) public static synchronized void globalMethod() { }9.2 ReentrantLock(显式锁,更灵活)需要手动 lock() / unlock(),必须放在 finally 中。高级功能:tryLock(timeout):超时放弃,避免死锁。lockInterruptibly():可中断抢锁。new ReentrantLock(true):公平锁(排队不插队)。多 Condition:精准唤醒指定线程。private final Lock lock = new ReentrantLock(); public void update() { lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); } }9.3 volatile(轻量级同步)只保证:可见性 + 禁止指令重排。不保证:原子性(不能用于 count++)。适用场景:布尔状态标记、单例双重检查中的 instance。private volatile boolean stop = false; public void run() { while (!stop) { /* 执行任务 */ } } public void stopTask() { stop = true; }9.4 原子类(无锁,CAS)AtomicInteger、AtomicLong、AtomicReference 等。高并发计数用 LongAdder(分段累加,性能更高)。private AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); // 原子+19.5 线程安全集合(必记两个)ConcurrentHashMap:分段锁 + CAS,高并发 Map。CopyOnWriteArrayList:读多写少场景,写时复制数组,不阻塞读。Map<String, Integer> map = new ConcurrentHashMap<>(); List<String> list = new CopyOnWriteArrayList<>();9.6 ThreadLocal(线程隔离)每个线程拥有自己的变量副本,不竞争。必须 remove(),否则内存泄漏(尤其在线程池中)。private ThreadLocal<String> userLocal = new ThreadLocal<>(); userLocal.set("张三"); String user = userLocal.get(); userLocal.remove(); // 用完必须清9.7 同步机制选择总结(背诵)场景推荐方案简单逻辑同步(库存、判断)synchronized需要超时、中断、公平锁ReentrantLock线程开关、状态标记volatile计数、累加、ID 生成AtomicInteger / LongAdder多线程操作 Map / ListConcurrentHashMap / CopyOnWriteArrayList单例中存线程私有数据ThreadLocal禁忌:❌ 不要用 volatile 做 i++❌ 不要在单例 Bean 中写普通成员变量(若多线程修改)❌ 不要用 HashMap / ArrayList 做并发写入❌ 不要只锁一半逻辑(如只锁修改,不锁判断)十、死锁10.1 死锁的四个必要条件(缺一不可)互斥:资源只能被一个线程占用。请求与保持:拿着自己的资源,还请求别人的资源。不可剥夺:资源不能被强行抢走。循环等待:线程间形成等待闭环。10.2 避免死锁的方法破坏任意一个条件(如规定锁的顺序、使用 tryLock 超时放弃)。十一、优雅停止线程废弃:stop()、suspend()、resume()(不安全)。推荐方式:中断机制:thread.interrupt() 设置中断标志,线程内检查 Thread.currentThread().isInterrupted() 自行停止。标志位:volatile boolean running,线程定期检查。// 标志位方式 public class MyTask implements Runnable { private volatile boolean running = true; public void stop() { running = false; } public void run() { while (running) { /* 任务 */ } // 清理资源 } }十二、线程间通信(5 种实战方式)方式说明常用场景共享变量(volatile / 原子类)一个线程改,另一个读状态标记、计数器wait()/notify()配合 synchronized生产者‑消费者Condition(配合 ReentrantLock)多路等待,精准唤醒复杂协调Semaphore控制同时访问的线程数限流、连接池CountDownLatch / CyclicBarrier线程协调工具主等子、互相等待12.1 Semaphore 示例Semaphore semaphore = new Semaphore(3); // 最多3个线程同时访问 semaphore.acquire(); // 获取许可(阻塞) semaphore.release(); // 释放许可12.2 CountDownLatch(倒计时门栓)CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(() -> { // 执行任务 latch.countDown(); }).start(); } latch.await(); // 主线程等待3个任务完成12.3 CyclicBarrier(循环栅栏)CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("三人齐了,出发")); for (int i = 0; i < 3; i++) { new Thread(() -> { barrier.await(); // 互相等待 }).start(); }十三、线程池(企业开发核心)13.1 为什么需要线程池资源复用(避免频繁创建/销毁线程)响应速度快(任务来了直接执行)可管理(控制并发数、监控)13.2 ThreadPoolExecutor 7 大核心参数(必背)new ThreadPoolExecutor( corePoolSize, // 核心线程数(常驻) maximumPoolSize, // 最大线程数(核心+临时) keepAliveTime, // 临时线程空闲存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂(一般用默认) RejectedExecutionHandler handler // 拒绝策略 );13.3 执行流程(5 步,必背)提交任务 → 核心线程未满 → 新建核心线程执行核心线程满 → 任务加入阻塞队列队列满 → 新建临时线程执行总线程数 = 最大线程数 → 执行拒绝策略临时线程空闲超时 → 销毁(回到核心线程数)13.4 4 种拒绝策略(背)策略行为比喻AbortPolicy(默认)抛 RejectedExecutionException直接赶走客人,还吵架CallerRunsPolicy调用者线程自己执行任务让老板自己服务客人DiscardPolicy悄悄丢弃任务,不报错悄悄赶走客人,不吭声DiscardOldestPolicy丢弃队列中最老的任务,然后重提赶走排队最久的,让新人进13.5 Executors 提供的 4 种常用线程池(开发规范禁止使用,需手动创建)工厂方法特点适用场景newFixedThreadPool(n)固定线程数,无界队列重负载服务器newCachedThreadPool()动态线程数,空闲60s回收大量短期异步任务newSingleThreadExecutor()单线程,顺序执行任务需顺序处理newScheduledThreadPool(n)支持定时/延迟执行定时任务规范:不要用 Executors 创建,因为 FixedThreadPool 使用无界队列可能 OOM,CachedThreadPool 可能创建过多线程。必须使用 new ThreadPoolExecutor 自定义。13.6 线程数设置公式(背)CPU 密集型(计算为主):线程数 = CPU 核心数 + 1IO 密集型(网络、磁盘):线程数 = CPU 核心数 × 2 (或更多)十四、定时任务线程池(ScheduledThreadPoolExecutor)14.1 三种定时任务类型方法作用特点schedule(Runnable, delay, unit)延迟执行一次-scheduleAtFixedRate(...)固定频率执行不关心上次任务是否完成,可能叠加scheduleWithFixedDelay(...)固定延迟执行必须等上次完成,再等延迟时间ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); scheduler.scheduleAtFixedRate(() -> System.out.println("ping"), 0, 2, TimeUnit.SECONDS);14.2 对比 Timer 的优势多线程,任务互不干扰(Timer 单线程,一个任务异常会影响全部)支持线程池复用十五、线程池优雅关闭(餐厅下班逻辑)挂出「停止接客」牌子 → shutdown()(不再接收新任务)让客人慢慢吃完 → awaitTermination(timeout, unit) 等待现有任务完成等了很久还没吃完 → 直接清场 → shutdownNow()(强制中断所有任务)executor.shutdown(); try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); }十六、性能调优与监控16.1 关键指标活跃线程数:< 最大线程数的 80%队列长度:< 队列容量的 70%任务完成数:持续增长,增长停滞说明处理异常拒绝任务数:应为 0,频繁拒绝需优化配置16.2 监控工具JMX(ThreadPoolExecutor 暴露的 getActiveCount() 等)Micrometer(配合 Prometheus + Grafana)jstack 检查死锁十七、快速记忆口诀创建线程四种法:继承、实现、Callable、池。同步锁的选择:简单用 syn,灵活用 Re,开关用 volatile,计数用原子类。线程池七参数:核心最大时单队工拒。关闭优雅三步走:shutdown → 等待 → shutdownNow。死锁四条件:互斥请求不剥夺,循环等待一把锁。
2026年04月03日
14 阅读
0 评论
0 点赞
2026-04-03
Java 集合框架笔记(基于 Java 8)
Java 集合框架笔记(基于 Java 8)一、集合框架概述集合框架是一个统一的架构,用于存储和操作一组对象。根接口是 Iterable,核心接口包括 Collection(单列集合)和 Map(双列集合)。1.1 接口继承关系图Iterable<E> ↓ Collection<E> ├── List<E> │ ├── ArrayList<E> │ ├── LinkedList<E> │ ├── Vector<E> │ └── Stack<E> ├── Set<E> │ ├── HashSet<E> │ ├── LinkedHashSet<E> │ └── SortedSet<E> (接口) │ └── TreeSet<E> └── Queue<E> ├── PriorityQueue<E> └── Deque<E> └── ArrayDeque<E> Map<K,V> (独立体系) ├── HashMap<K,V> ├── LinkedHashMap<K,V> ├── Hashtable<K,V> └── SortedMap<K,V> (接口) └── TreeMap<K,V>1.2 核心接口简介接口描述特点Collection所有单列集合的根接口定义基本操作如添加、删除、大小等List有序集合,允许重复元素支持索引访问,元素顺序与插入顺序一致Set不允许重复元素无索引,通常基于哈希或排序实现Queue队列(先进先出)支持队首队尾操作,也有双端队列 DequeMap键值对映射键唯一,每个键映射到一个值二、Collection 接口所有集合类的根接口,定义了集合的基本操作。2.1 基本方法方法描述boolean add(E e)添加元素boolean remove(Object o)移除元素int size()返回元素个数boolean isEmpty()是否为空void clear()清空所有元素boolean contains(Object o)是否包含某元素Iterator<E> iterator()返回迭代器Object[] toArray()转换为数组三、List 接口有序集合,允许重复元素,支持通过索引操作。3.1 List 特有方法方法描述E get(int index)获取指定位置元素E set(int index, E element)替换指定位置元素void add(int index, E element)在指定位置插入元素E remove(int index)删除指定位置元素int indexOf(Object o)返回首次出现的索引int lastIndexOf(Object o)返回最后一次出现的索引List<E> subList(int from, int to)获取子列表3.2 ArrayList数据结构:动态数组 初始容量:10(默认构造函数) 扩容机制:新容量 = 旧容量 + 旧容量 >> 1(即 1.5 倍) 时间复杂度:get(index) / set(index, element):O(1)add(E e) 平均 O(1),最坏 O(n)(扩容)add(index, E e) / remove(index):O(n) 线程安全:否创建方式// 默认初始容量10 ArrayList<String> list1 = new ArrayList<>(); // 指定初始容量 ArrayList<String> list2 = new ArrayList<>(20); // 从其他集合创建(Java 9+ List.of 返回不可变List) List<String> source = List.of("Java", "Python", "C++"); ArrayList<String> list3 = new ArrayList<>(source); // 使用接口引用(推荐) List<String> list4 = new ArrayList<>();常用操作// 添加 list.add("Java"); // 末尾添加 list.add(1, "C++"); // 指定位置插入 list.addAll(List.of("Go", "Rust")); // 添加多个 // 访问与修改 String first = list.get(0); String last = list.get(list.size() - 1); list.set(1, "Python"); // 查找 int idx = list.indexOf("Java"); boolean has = list.contains("C++"); // 删除 list.remove(0); // 按索引 list.remove("Java"); // 按对象(删除第一个匹配) list.removeAll(List.of("Go", "Rust")); list.removeIf(s -> s.length() > 4); // Java 8+ list.clear(); // 缩容释放内存 list.trimToSize();线程安全解决方案Collections.synchronizedList(new ArrayList<>())CopyOnWriteArrayList(读多写少场景)手动 synchronized 同步注意事项遍历时(增强 for 或迭代器)不能直接调用 list.remove() 或 list.add(),应使用迭代器的 remove() 或 removeIf()。List.of() 返回的集合不可修改。3.3 LinkedList数据结构:双向链表 时间复杂度:get(index) / set(index, element):O(n)addFirst / addLast / removeFirst / removeLast:O(1)中间插入/删除:O(n)(需先定位)特点:实现 List 和 Deque 接口,可作列表、队列、双端队列、栈。创建与添加LinkedList<String> list = new LinkedList<>(); List<String> list2 = new LinkedList<>(); // 接口引用 list.add("Apple"); // 末尾 list.addFirst("Grape"); // 头部 list.addLast("Banana"); // 尾部 list.add(1, "Orange"); // 指定位置 list.offer("Cherry"); // 队列入队(末尾)访问操作String first = list.getFirst(); String last = list.getLast(); String element = list.get(2); // O(n) // 队列/双端队列查看 String head = list.peek(); String headFirst = list.peekFirst(); String headLast = list.peekLast(); int index = list.indexOf("Apple");删除操作list.removeFirst(); list.removeLast(); list.remove(1); list.remove("Apple"); // 队列出队 String polled = list.poll(); list.pollFirst(); list.pollLast();作为队列(FIFO)Queue<String> queue = new LinkedList<>(); queue.offer("客户1"); queue.offer("客户2"); String head = queue.peek(); // 查看队首 String customer = queue.poll(); // 出队作为栈(LIFO)Deque<String> stack = new LinkedList<>(); stack.push("方法A"); stack.push("方法B"); String top = stack.peek(); // 查看栈顶 String method = stack.pop(); // 出栈作为双端队列Deque<Integer> deque = new LinkedList<>(); deque.addFirst(2); deque.addLast(3); deque.addFirst(1); deque.addLast(4); // 结果 [1,2,3,4] deque.peekFirst(); deque.peekLast(); deque.removeFirst(); deque.removeLast();注意事项get(index) 在大列表上频繁使用会导致性能问题(O(n))。没有 trimToSize 方法(链表动态分配)。3.4 Vector 和 StackVector:早期动态数组,方法使用 synchronized 实现线程安全,性能较差,现已不推荐使用。Stack:继承 Vector,提供栈操作 push、pop、peek。同样不推荐,建议使用 Deque 实现(如 ArrayDeque)。四、Set 接口不允许重复元素(最多一个 null),无索引。底层依赖 equals() 和 hashCode() 判断重复。4.1 实现类对比实现类排序性能允许 null线程安全适用场景HashSet无序最高是否一般用途,高性能LinkedHashSet插入顺序中等是否需要保持插入顺序TreeSet自然排序/定制排序较低否否需要排序和范围查询4.2 使用示例Set<String> fruits = new HashSet<>(); fruits.add("苹果"); fruits.add("香蕉"); fruits.add("苹果"); // 重复,不会添加 System.out.println(fruits.size()); // 24.3 最佳实践默认使用 HashSet,需要有序用 LinkedHashSet,需要排序用 TreeSet。线程安全包装:Collections.synchronizedSet(new HashSet<>()) 或 ConcurrentHashMap.newKeySet()。自定义对象存入 Set 时必须重写 equals() 和 hashCode();存入 TreeSet 还需实现 Comparable 或提供 Comparator。合理设置 HashSet 初始容量(避免频繁扩容),负载因子默认 0.75。4.4 注意事项HashSet 迭代顺序不确定,不要依赖。TreeSet 不允许 null,会抛出 NullPointerException。修改已添加到 Set 中的对象可能导致数据丢失或重复。五、Queue 接口FIFO 队列,提供队首队尾操作。Deque 子接口支持双端操作。5.1 主要方法方法描述失败时offer(E e)入队返回 falsepoll()出队并移除返回 nullpeek()查看队首不移除返回 nulladd(E e)入队抛出异常remove()出队并移除抛出异常element()查看队首抛出异常推荐使用:offer、poll、peek(避免异常)。5.2 实现类对比实现类数据结构是否支持 null特点LinkedList双向链表是同时实现 List 和 Deque,插入删除效率高ArrayDeque动态数组否性能优于 LinkedList,一般队列首选PriorityQueue堆(数组)否按优先级排序(自然排序或 Comparator)5.3 使用示例Queue<String> queue = new ArrayDeque<>(); queue.offer("A"); queue.offer("B"); String head = queue.peek(); // A String out = queue.poll(); // A // 优先级队列 Queue<Integer> pq = new PriorityQueue<>(); pq.offer(5); pq.offer(1); pq.offer(3); System.out.println(pq.poll()); // 15.4 线程安全队列ArrayBlockingQueue(有界阻塞队列)LinkedBlockingQueue(可选有界)PriorityBlockingQueue(无界优先级队列)六、Map 接口键值对映射,键唯一,值可重复。不继承 Collection。6.1 常用方法方法描述V put(K key, V value)添加键值对,返回旧值V get(Object key)根据键获取值V remove(Object key)删除键值对boolean containsKey(Object key)是否包含键boolean containsValue(Object value)是否包含值int size()元素个数Set<K> keySet()返回所有键的 Set 视图Collection<V> values()返回所有值的 Collection 视图Set<Map.Entry<K,V>> entrySet()返回所有键值对的 Set 视图6.2 实现类对比实现类排序允许 null线程安全底层结构适用场景HashMap无序键/值均可否哈希表通用,最高性能LinkedHashMap插入顺序/访问顺序键/值均可否哈希表+双向链表需要保持顺序TreeMap自然排序/定制排序键不可为 null否红黑树需要排序和范围查询Hashtable无序键/值均不可是(全表锁)哈希表已淘汰,不推荐6.3 使用示例Map<String, Integer> map = new HashMap<>(); map.put("apple", 10); map.put("banana", 20); int value = map.get("apple"); // 10 map.remove("banana"); // 遍历 for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + "=" + entry.getValue()); }6.4 最佳实践声明时使用接口类型:Map<K,V> map = new HashMap<>()。作为键的类必须重写 equals() 和 hashCode(),最好使用不可变对象。合理设置 HashMap 初始容量,避免频繁 rehash。多线程环境使用 ConcurrentHashMap(不要使用 Hashtable)。七、迭代器(Iterator)7.1 Iterator 接口统一遍历集合的方式,支持安全删除。Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (condition) it.remove(); // 安全删除 }方法:boolean hasNext()E next()void remove()(可选)7.2 ListIterator 接口Iterator 的子接口,专门用于 List,支持双向遍历和修改。额外方法:boolean hasPrevious()E previous()int nextIndex() / int previousIndex()void set(E e) 修改当前元素void add(E e) 在当前位置插入7.3 增强 for 循环(for-each)语法糖,编译器自动转换为 Iterator 遍历。int[] numbers = {1,2,3,4,5}; for (int num : numbers) { System.out.println(num); } List<String> list = ...; for (String s : list) { // 不能修改 list 结构(添加/删除) }限制:无法获取当前索引遍历时不能修改集合结构(否则抛出 ConcurrentModificationException)不能修改基本类型数组的元素值(但可以修改引用类型对象的属性)八、并发集合(java.util.concurrent)8.1 ConcurrentHashMapJava 7 及之前:分段锁(Segment),默认 16 个段。Java 8+:Node 数组 + CAS + synchronized 对链表/红黑树头节点加锁,锁粒度更细。支持并发读(无锁)和并发写(部分加锁)。不允许 null 键/值。8.2 CopyOnWriteArrayList写时复制策略:修改时复制整个数组,修改后替换原数组。读操作无锁,性能极高。适用于读多写极少的场景(如配置列表、监听器列表)。缺点:写操作内存开销大,数据一致性弱(可能读到旧数据)。8.3 BlockingQueue 阻塞队列实现类特点ArrayBlockingQueue有界,基于数组,FIFO,支持公平/非公平锁LinkedBlockingQueue可选有界(默认 Integer.MAX_VALUE),基于链表PriorityBlockingQueue无界,按优先级排序核心阻塞方法:put(E e):队列满时阻塞take():队列空时阻塞典型应用:生产者-消费者模式。九、选择指南9.1 根据数据结构选择需求推荐实现需要索引访问List(ArrayList / LinkedList)不允许重复元素Set(HashSet / LinkedHashSet / TreeSet)键值对存储Map(HashMap / LinkedHashMap / TreeMap)FIFO 队列Queue(ArrayDeque 首选)频繁随机访问ArrayList频繁插入/删除(中间)LinkedList需要排序TreeSet / TreeMap保持插入顺序LinkedHashSet / LinkedHashMap线程安全(单元素操作)Vector / Hashtable(不推荐),推荐 ConcurrentHashMap、CopyOnWriteArrayList 等9.2 时间复杂度速查操作ArrayListLinkedListHashSetTreeSet添加(末尾)O(1)*O(1)O(1)**O(log n)插入(中间)O(n)O(n)--删除(中间)O(n)O(n)O(1)**O(log n)按值查找O(n)O(n)O(1)**O(log n)按索引查找O(1)O(n)--扩容时 O(n) ** 假设哈希函数良好十、常见问题与注意事项ConcurrentModificationException:在遍历集合时直接修改结构(增删),应使用迭代器的 remove() 或 removeIf()。HashSet / HashMap 中自定义对象:必须正确重写 equals() 和 hashCode(),否则无法去重或查找。TreeSet / TreeMap 中元素:必须实现 Comparable 或传入 Comparator,否则抛出 ClassCastException。ArrayList 删除元素后不会自动缩容:可调用 trimToSize() 释放多余内存。LinkedList 的 get(index):效率低(O(n)),避免在循环中频繁调用。Arrays.asList() 返回的 List:是固定大小的视图,不支持 add / remove,会抛出 UnsupportedOperationException。优先使用接口类型声明:List<String> list = new ArrayList<>();,便于更换实现。
2026年04月03日
16 阅读
0 评论
0 点赞
2026-04-03
Java基础笔记
Java基础笔记一、数据类型与变量1. 8种基本数据类型(4大类)类型关键字字节默认值包装类整型byte10Byte short20Short int40Integer long80LLong浮点型float40.0fFloat double80.0dDouble字符型char2'\u0000' (空)Character布尔型boolean1位falseBooleanlong 后缀 L,float 后缀 f,char 字面量用单引号。所有数值类型默认值都是 0(或 0.0)。2. 变量类型局部变量:声明在方法/构造/代码块中,必须显式初始化,作用域仅限于当前块,存储在栈内存。实例变量:声明在类中但方法外,属于对象,有默认值,存储在堆内存,随对象销毁。类变量(静态变量):使用 static 声明,属于类,所有实例共享,类加载时初始化,可通过类名直接访问,存储在方法区(元空间)。3. 类型转换自动类型转换:小范围→大范围,编译器自动完成,不会丢失精度(long→float 可能丢失精度)。强制类型转换:大范围→小范围,需显式使用 (类型),可能丢失精度或溢出。包装类转换:自动装箱/拆箱。例如 Integer b = 10;(装箱),int d = b;(拆箱)。 注意:-128~127 范围内的 Integer 会被缓存,复用同一对象。字符串转换:基本类型→字符串用 String.valueOf(),字符串→基本类型用 parseXxx()。二、运算符与表达式优先级:* / % > + - > == != > && > ||。建议使用括号明确顺序。复合赋值:+=、-= 等可简化代码。浮点数比较:使用误差范围(如 Math.abs(a - b) < 1e-6),避免直接 ==。字符串比较:== 比较引用地址。equals() 比较内容。compareTo() 返回大小关系(正/零/负)。自增/自减:避免在复杂表达式中使用,如 i++ + ++i。三、输入输出(Scanner)Scanner sc = new Scanner(System.in); int n = sc.nextInt(); // 读取整数 double d = sc.nextDouble(); // 读取浮点数 String s1 = sc.next(); // 读到空格或换行 String s2 = sc.nextLine(); // 读取整行 boolean b = sc.nextBoolean(); // 检查方法 if (sc.hasNextInt()) { ... }缓存问题:nextInt() 后调用 nextLine() 需先执行一次 nextLine() 吸收换行符。资源关闭:sc.close(),建议使用 try-catch 处理异常。格式化输出:System.out.printf("%.2f", d);四、数组1. 一维数组声明:int[] arr; (不能指定长度)创建:arr = new int[5]; → 元素自动初始化为默认值 [0,0,0,0,0]静态初始化:int[] arr = {1,2,3}; 或 new int[]{1,2,3}动态初始化:先声明后创建,再逐个赋值。访问:索引从0开始,arr[0],注意 ArrayIndexOutOfBoundsException。长度:arr.length(不是方法)。2. 多维数组(以二维为例)int[][] matrix = new int[3][4]; // 规则3行4列 int[][] ragged = new int[3][]; // 不规则 ragged[0] = new int[2]; ragged[1] = new int[5];获取行数:matrix.length,获取当前行列数:matrix[i].length。遍历时注意每一行长度可能不同。3. 数组操作复制:System.arraycopy()(原生高效)、Arrays.copyOf()、clone()。排序:Arrays.sort(arr)(Dual-Pivot Quicksort)、手写冒泡/选择/快排。搜索:线性搜索(未排序)。二分搜索(已排序):Arrays.binarySearch(arr, key),返回下标或负数插入点。五、类与对象1. 类的组成实例变量/方法:属于对象,通过对象访问。静态变量/方法:属于类,通过类名访问,不能直接访问实例成员。构造函数:与类同名,无返回类型,可重载。一旦定义了有参构造,默认无参构造不再自动提供。2. 封装使用 private 隐藏字段,提供 public 的 getter/setter。保护内部实现,提供清晰的公共接口。3. 方法定义:[修饰符] [static] 返回值 方法名(参数) { ... }调用:静态方法:类名.方法名()实例方法:对象.方法名()参数传递:基本类型:传值副本,不影响原变量。引用类型:传引用副本,可修改对象内容,但不能改变原引用指向。4. 构造方法链式调用使用 this(参数) 调用同一类的另一个构造方法,必须是构造方法的第一条语句。public class Person { private String name; public Person() { this("Unknown"); } public Person(String name) { this.name = name; } }5. this 关键字引用当前对象,用于区分成员变量和参数。不能在静态上下文中使用。this() 必须是构造方法第一条语句。6. 访问修饰符修饰符同类同包子类(不同包)任意private✔✘✘✘default✔✔✘✘protected✔✔✔✘public✔✔✔✔7. 包(package)文件第一行非注释代码:package com.example.util;import 语句位于 package 之后,类定义之前。静态导入:import static java.lang.Math.PI; 可直接使用 PI。六、继承使用 extends 关键字,Java 只支持单继承。所有类隐式继承 Object。方法重写(Override):子类重新定义父类方法,方法签名必须相同。使用 @Override 注解。访问修饰符不能比父类更严格(父类 protected → 子类 public 可以,反之不行)。返回类型可以是子类类型(协变返回)。不能抛出比父类更宽泛的检查异常。final 方法不能被重写;static 方法不能被重写(但可以隐藏);private 方法不可见,不能重写。重载(Overload):同一类中方法名相同,参数列表不同。super 关键字:调用父类构造方法:super() 必须是子类构造的第一条语句。访问父类成员:super.method()、super.field。不能与 this() 同时使用。final 关键字final 类:不能被继承(如 String、Integer)。final 方法:不能被重写。final 变量:静态 final:声明时或静态代码块初始化。实例 final:声明时或构造方法中初始化。局部 final:使用前赋值即可。参数 final:方法内不能修改参数值。七、抽象类与接口特性抽象类(abstract class)接口(interface)关键字abstract classinterface继承/实现单继承多实现构造方法有无成员变量可以是实例变量或常量只能是 public static final 常量方法抽象方法 + 具体方法抽象方法 + default/static 方法(JDK8+)访问修饰符任意方法默认 public abstract使用场景代码复用、模板方法模式定义规范、多继承能力抽象类不能实例化,子类必须实现所有抽象方法(除非子类也是抽象类)。接口中的 default 方法提供默认实现,static 方法属于接口本身。八、多态实现条件:继承 + 重写 + 父类引用指向子类对象。编译时多态:方法重载。运行时多态:通过父类引用调用被子类重写的方法,实际执行子类版本。向上转型:Parent p = new Child(); 安全,自动进行。向下转型:Child c = (Child) p; 可能抛出 ClassCastException,推荐先用 instanceof 检查。instanceof:object instanceof ClassName 返回 true/false。用于类型检查,避免转换异常。null instanceof X 总是 false。不能用于基本类型。注意:静态方法、私有方法、构造方法不支持多态。九、内部类与嵌套类类型关键字能否访问外部类实例成员持有外部类引用能否定义静态成员创建方式成员内部类无✔✔不能(常量除外)外部类实例.new 内部类()静态嵌套类static✘(只能访问外部静态)✘✔new 外部类.静态嵌套类()局部内部类方法内✔✔不能在作用域内直接 new匿名类无名字✔✔不能new 接口/父类() { ... }内存泄漏风险:成员内部类隐式持有外部类引用,可能导致外部类无法被GC。局部内部类访问局部变量:变量必须是 final 或事实不可变(effectively final)。十、枚举(Enum)enum Color { RED, GREEN, BLUE }继承 java.lang.Enum,不能手动继承其他类。常用方法:name():返回常量名(如 "RED")ordinal():返回索引(从0开始)toString():默认返回 name()valueOf(String):根据名称获取枚举常量values():返回所有枚举常量的数组compareTo():比较顺序(基于 ordinal)避免在业务逻辑中使用 ordinal(),因为顺序改变会出错。可添加字段和构造方法来实现语义属性。十一、注解(Annotation)内置注解:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs、@FunctionalInterface。元注解(用于自定义注解):@Retention:指定生命周期(SOURCE、CLASS、RUNTIME)@Target:指定可放置位置(TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR 等)@Documented:生成Javadoc时包含该注解@Inherited:允许子类继承父类的注解@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAnnotation { String value() default ""; }十二、泛型参数化类型:List<String> 保证类型安全,避免强制转换。泛型类/接口:class Box<T> { private T t; ... }泛型方法:public <T> T getValue(T t) { return t; }通配符:?? extends T:上界通配符,用于读取(生产者)。? super T:下界通配符,用于写入(消费者)。PECS原则:Producer Extends, Consumer Super。有界类型参数:<T extends Number> 限定 T 必须是 Number 或其子类。注意:泛型在编译时擦除,运行时无法获知具体类型参数。十三、反射(Reflection)获取 Class 对象的三种方式:类名.class(编译时确定)对象.getClass()(运行时)Class.forName("全限定类名")(动态加载)常用操作:创建实例:clazz.newInstance() 或构造器 newInstance()获取方法:getMethod()、getDeclaredMethod()调用方法:method.invoke(obj, args)访问字段:getField()、setAccessible(true)反射会破坏封装,带来性能开销,慎用。十四、递归定义:方法调用自身。线性递归:每次调用一个子问题,深度 = 规模,时间复杂度 O(n)。二分递归:每次调用两个子问题,如斐波那契 fib(n-1) + fib(n-2),存在重复计算,可优化(记忆化)。注意防止栈溢出,确保有终止条件。十五、String 字符串不可变:任何修改都会产生新字符串对象。字符串池:字面量 "abc" 会放入常量池,复用相同内容;new String("abc") 创建堆中新对象,不自动入池,可调用 intern() 手动入池。常用方法:length()、isEmpty()、isBlank()(Java 11+)indexOf()、lastIndexOf()equals()、equalsIgnoreCase()、compareTo()contains()、startsWith()、endsWith()substring()replace()、replaceAll()trim()、strip()(Java 11+)toUpperCase()、toLowerCase()split()、join()高效拼接:使用 StringBuilder(线程不安全)或 StringBuffer(线程安全)。十六、代码最佳实践提示使用括号明确运算优先级。浮点数比较使用误差范围。字符串内容比较用 equals() 而非 ==。关闭 Scanner 资源,处理异常。数组遍历注意边界,多用 length 属性。重写方法时加 @Override。优先使用多态和接口而非 instanceof 链。枚举代替常量整数。泛型提供编译时类型安全。合理使用内部类,注意内存泄漏。反射仅用于框架或特殊场景。
2026年04月03日
12 阅读
0 评论
1 点赞
2026-03-30
MySQL SQL性能调优实战指南
# MySQL SQL性能调优实战指南一、核心思想:系统化调优四步法SQL调优不是盲目加索引,而是一个系统化工程,应遵循 测量 -> 分析 -> 优化 -> 验证 的闭环流程。测量:通过慢查询日志、监控系统定位需要优化的慢SQL。分析:使用EXPLAIN等工具分析SQL的执行计划,找到性能瓶颈。优化:根据分析结果,采取索引优化、SQL重写、架构调整等手段。验证:在测试环境验证优化效果,并上线监控。二、性能诊断与分析工具1. 慢查询日志作用:记录执行时间超过阈值的SQL语句。配置:-- 开启慢查询日志 SET GLOBAL slow_query_log = ON; -- 设置阈值(如0.1秒) SET GLOBAL long_query_time = 0.1; -- 查看日志文件路径 SHOW VARIABLES LIKE 'slow_query_log_file';2. EXPLAIN 执行计划分析作用:查看MySQL如何执行你的SQL,是调优的核心工具。关键字段解读:type:访问类型,性能从好到差:system > const > eq_ref > ref > range > index > ALL。ALL:全表扫描,性能最差,必须优化。range:范围扫描,使用了索引,常见于BETWEEN、>、LIKE 'abc%'。ref:非唯一性索引查找。possible_keys:可能用到的索引。key:实际使用的索引。rows:MySQL估计需要扫描的行数,这个值越小越好。Extra:额外信息,关键提示:Using index:覆盖索引,查询的列都在索引中,无需回表,性能高。Using where:需要回表过滤数据。Using filesort:文件排序,需要优化(通常需要为ORDER BY字段建立索引)。Using temporary:使用临时表,常见于GROUP BY或DISTINCT,性能差。使用方式:EXPLAIN SELECT * FROM users WHERE age > 25; EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age > 25; -- 更详细的结构化信息 -- MySQL 8.0+ 可以查看实际执行计划 EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;三、索引优化策略(核心)索引是提升查询性能的关键,但也是一把双刃剑(占用空间、影响写入性能)。1. 索引设计原则高选择性字段优先:字段的唯一性越高,索引效果越好。-- 计算选择性:值越接近1,选择性越高 SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM orders;最左前缀原则:复合索引 (a, b, c) 会创建 (a), (a,b), (a,b,c) 三个索引。查询条件必须包含索引的最左列,否则索引失效。索引列顺序:等值查询放前面,范围查询放后面。避免冗余索引:尽量复用现有索引,删除无用索引。-- 查看表上的索引 SHOW INDEX FROM table_name;2. 索引优化实战覆盖索引:查询的列都在索引中,避免回表。-- 假设有复合索引 idx_status_covering(status, user_id, order_no) SELECT user_id, order_no FROM orders WHERE status = 'pending'; -- 走覆盖索引范围查询优化:-- 原始(慢):索引可能只用到create_time CREATE INDEX idx_time ON orders(create_time); SELECT * FROM orders WHERE create_time BETWEEN '2024-01-01' AND '2024-01-31' AND status = 'completed'; -- 优化后:将等值条件放在前面 CREATE INDEX idx_status_time ON orders(status, create_time);3. 索引失效的常见场景(避坑指南)函数操作:对索引列使用函数或计算。-- ❌ 失效 SELECT * FROM users WHERE DATE(create_time) = '2024-01-01'; -- ✅ 优化 SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';隐式类型转换:数据类型不匹配。-- ❌ 失效(phone是varchar类型) SELECT * FROM users WHERE phone = 13800138000; -- ✅ 优化 SELECT * FROM users WHERE phone = '13800138000';前导通配符:LIKE以%开头。-- ❌ 失效 SELECT * FROM users WHERE name LIKE '%john%'; -- ✅ 可能有效(后缀通配符) SELECT * FROM users WHERE name LIKE 'john%';OR条件:如果OR连接的条件中有一个没有索引,整个查询都可能不走索引。NOT IN 或 !=:可能导致索引失效(或效果变差)。四、SQL语句优化技巧1. 查询重写用JOIN代替子查询:子查询会创建临时表,JOIN通常性能更好。-- ❌ 低效的子查询 SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE vip_level > 3); -- ✅ 高效JOIN SELECT o.* FROM orders o JOIN users u ON o.user_id = u.id WHERE u.vip_level > 3;用EXISTS代替IN:当外表数据量大时,EXISTS可能更优。避免SELECT *:只查询需要的字段,特别是对于TEXT/BLOB等大字段。HAVING改为WHERE:HAVING用于分组后过滤,尽量将过滤条件放在WHERE中提前过滤。2. 分页查询优化问题:LIMIT 1000000, 20 会扫描100万条记录,性能极差。方案一:游标分页:基于有序且唯一的列(如自增ID)进行翻页。-- 每次记录上一页最后一条的ID SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;方案二:延迟关联:先通过覆盖索引查询出主键,再回表获取其他字段。SELECT * FROM orders o JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 20) t ON o.id = t.id;3. 大数据量处理分批处理:避免一次性操作大量数据,应分批次循环处理。-- 每次处理1000条 UPDATE orders SET status = 'processed' WHERE status = 'pending' LIMIT 1000;分区表:对于历史数据,可以使用分区表进行物理隔离,按时间或键值分区。CREATE TABLE logs ( id BIGINT, log_date DATE ) PARTITION BY RANGE (YEAR(log_date)) ( PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025) );五、数据库架构与配置优化1. 架构优化读写分离:主库负责写入,从库负责读取,分散压力。分库分表:当单表数据量过大(如超过千万级)时,垂直分库(按业务拆分)或水平分表(按ID取模)。引入缓存:热点数据(如用户信息、商品详情)使用Redis等缓存,减少数据库压力。2. 配置参数调优(关键)在my.cnf或my.ini中调整:InnoDB缓冲池(最重要):设置为物理内存的50%-80%。innodb_buffer_pool_size = 16G日志缓冲:innodb_log_buffer_size = 64M连接数:max_connections = 1000排序/临时表:sort_buffer_size = 2M tmp_table_size = 64M六、实战调优案例:电商订单查询场景查询用户近3个月内状态为pending或shipped的订单,按时间倒序取前20条。SELECT * FROM orders WHERE user_id = 100 AND status IN ('pending', 'shipped') AND create_time BETWEEN '2024-01-01' AND '2024-03-01' ORDER BY create_time DESC LIMIT 20;问题分析原表可能没有索引,导致全表扫描。ORDER BY导致Using filesort。优化步骤分析:EXPLAIN 显示 type=ALL,Extra=Using where; Using filesort。建立复合索引:遵循最左前缀原则,将等值查询的字段user_id和status放在前面,范围查询字段create_time放在后面,且确保排序的字段也在索引中。CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);改写查询:利用覆盖索引,只查需要的字段,减少回表。SELECT id, user_id, order_no, amount, create_time FROM orders WHERE user_id = 100 AND status IN ('pending', 'shipped') AND create_time BETWEEN '2024-01-01' AND '2024-03-01' ORDER BY create_time DESC LIMIT 20;验证:再次EXPLAIN,type变为range或ref,rows大幅减少,Extra中不再有Using filesort,且出现Using index(覆盖索引)。七、调优检查清单[ ] 慢查询日志:是否已开启并定期分析?[ ] 索引设计:高频查询条件是否有索引?复合索引顺序是否合理?[ ] 索引失效:是否对索引列使用了函数、计算、隐式转换?[ ] SQL写法:是否避免了SELECT *?分页是否使用了大偏移量?[ ] 表结构:字段类型是否选择合适(如能用int就不用varchar)?[ ] 架构层面:读写分离、缓存、分库分表是否按需引入?[ ] 配置参数:缓冲池大小是否合理?连接数是否充足?总结MySQL SQL调优的核心是通过EXPLAIN理解SQL的执行路径,并利用索引减少需要扫描的数据行数。它是一个持续优化的过程,需要结合业务场景、数据规模和系统架构来综合考量。记住口诀:测量先行,索引为王,SQL避坑,架构护航。
2026年03月30日
8 阅读
0 评论
1 点赞
2026-03-27
答辩
校园二手交易系统答辩问题及回答一、系统架构与技术选型1. 请介绍一下系统的整体架构设计回答:本系统采用B/S三层架构模式构建,包括前端展示层、后端服务层和数据持久化层。前端展示层:基于Vue 3框架构建,使用Vant移动端组件库开发响应式界面。采用单页面应用(SPA)模式,通过Vue Router管理路由导航,使用Pinia进行全局状态管理,采用组件化开发模式提升代码复用性。后端服务层:基于NestJS框架开发,采用模块化方式组织代码结构。核心业务模块包括认证授权模块、用户信息模块、商品资源模块、交易订单模块、信用档案模块、后台管理模块等。每个模块内部遵循控制器-服务-数据访问对象的分层设计。数据持久化层:采用MySQL 8.0作为关系型数据库,引入Redis作为缓存层存储会话Token和热点数据,提升系统响应效率。代码位置:后端入口:back-end/src/main.ts模块配置:back-end/src/app.module.ts前端路由:frontEnd/src/router/index.ts2. 为什么选择NestJS作为后端框架?与Express有什么区别?回答:选择NestJS主要基于以下考虑:架构规范性:NestJS提供了完整的架构支撑体系,包括依赖注入、模块化组织、切面编程等特性,强制开发者遵循最佳实践,代码结构更清晰。TypeScript原生支持:NestJS完全采用TypeScript编写,与前端Vue 3的TypeScript开发形成统一的技术栈,类型定义一致,降低沟通成本。内置功能丰富:Guards组件实现权限访问控制Interceptors组件统一处理日志输出与响应格式标准化Pipes组件完成请求参数校验Filters组件统一异常处理与Express的关系:NestJS底层使用Express作为HTTP服务器,但在此基础上提供了更高层次的抽象和结构化支持。代码示例:// back-end/src/app.module.ts - 模块化组织 @Module({ imports: [ AuthModule, UsersModule, GoodsModule, OrdersModule, // ... ], providers: [ { provide: APP_FILTER, useClass: ApiExceptionFilter }, { provide: APP_INTERCEPTOR, useClass: ApiResponseInterceptor }, ], })3. 系统是如何实现前后端分离的?API是如何设计的?回答:系统采用RESTful API设计风格实现前后端分离:API设计规范:统一前缀:/api/v1资源命名:使用复数名词(如/goods、/orders)HTTP方法语义:GET查询、POST创建、PATCH更新、DELETE删除统一响应格式:{ code, message, data }接口文档:集成Swagger自动生成API文档,访问/api/docs查看认证机制:采用JWT双令牌认证,Access Token用于接口访问,Refresh Token用于令牌刷新代码位置:API文档配置:back-end/src/main.ts统一响应拦截器:back-end/src/common/interceptors/api-response.interceptor.ts统一异常过滤器:back-end/src/common/filters/api-exception.filter.ts4. 为什么选择Vue 3而不是Vue 2?组合式API有什么优势?回答:选择Vue 3的原因:性能提升:Vue 3重写了虚拟DOM算法,编译优化更智能,运行时性能更好组合式API优势:更好的逻辑复用:通过自定义Hook提取可复用逻辑更好的TypeScript支持:类型推断更准确更灵活的代码组织:相关逻辑可以放在一起,不再受选项式API的分散限制Tree-shaking支持:按需引入API,打包体积更小代码示例:// frontEnd/src/stores/auth.ts - 组合式API风格 export const useAuthStore = defineStore('auth', () => { const user = ref<User | null>(null) const token = ref<string | null>(localStorage.getItem('token')) const isAuthenticated = computed(() => !!token.value) const login = async (params: LoginParams) => { const response = await authApi.login(params) setToken(response.accessToken) // ... } return { user, token, isAuthenticated, login } })二、认证授权模块5. 请详细说明JWT双令牌认证机制的实现回答:系统采用Access Token + Refresh Token双令牌机制:Access Token:有效期:2小时载荷:用户ID、角色、唯一标识符(jti)用途:接口访问鉴权Refresh Token:有效期:7天载荷:用户ID、唯一标识符存储:Redis中存储,key为refresh:token:{userId}用途:刷新会话令牌旋转策略:每次刷新生成新的Refresh Token,旧Token立即失效,防止令牌被窃取后长期有效黑名单机制:用户登出时将Access Token的jti加入Redis黑名单,防止已登出的Token继续使用代码位置:back-end/src/modules/auth/auth.service.ts核心代码:// 签发Access Token private async signAccessToken(userId: number, role: number) { const jti = randomUUID(); const expiresInSeconds = 60 * 60 * 2; // 2小时 const accessToken = await this.jwt.signAsync( { sub: userId, role, jti }, { expiresIn: expiresInSeconds }, ); return { accessToken, expiresIn: expiresInSeconds, jti }; } // 用户登出 - 加入黑名单 async logout(userId: number, accessJti: string, refreshToken: string) { await this.redis.set(`jwt:blacklist:${accessJti}`, '1', 'EX', 60 * 60 * 24); // 删除Refresh Token await this.redis.del(`refresh:token:${userId}`); }6. 密码是如何加密存储的?为什么选择bcrypt?回答:系统使用bcrypt算法对密码进行加密存储:bcrypt特点:自带盐值:每次加密自动生成随机盐,防止彩虹表攻击计算成本可调:通过cost factor控制计算复杂度,抵御暴力破解单向加密:无法反向解密,只能通过比较验证实现方式:// 注册时加密 const passwordHash = await bcrypt.hash(dto.password, 10); // cost factor = 10 // 登录时验证 const ok = await bcrypt.compare(dto.password, user.password);安全性:即使数据库泄露,攻击者也无法获取原始密码代码位置:back-end/src/modules/auth/auth.service.ts 第62行、第132行7. 如何实现用户权限控制?管理员和普通用户如何区分?回答:系统通过角色字段实现权限控制:角色定义:role=1:普通用户role=2:管理员JWT载荷携带角色信息:{ sub: userId, role, jti }Guard守卫实现:JwtAuthGuard:验证JWT有效性AdminGuard:验证是否为管理员角色路由级别控制:// back-end/src/modules/admin/admin.guard.ts canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const user = request.user; if (user.role !== 2) { throw new ForbiddenException('需要管理员权限'); } return true; }前端路由守卫:// frontEnd/src/router/index.ts if (roles && roles.includes('admin') && userRole !== 2) { next('/home') return }三、商品资源模块8. 商品状态流转是如何设计的?有哪些状态?回答:商品状态采用状态机模式设计,共7种状态:状态值状态名称说明0PENDING_AUDIT待审核1ONLINE在售2SOLD已售空3OFFLINE下架4REJECTED审核拒绝5VIOLATION违规商品6DELETED软删除状态流转规则:发布 → 待审核(0)审核通过 → 在售(1)审核拒绝 → 审核拒绝(4)库存为0 → 已售空(2)卖家主动下架 → 下架(3)管理员标记违规 → 违规商品(5)代码位置:back-end/src/modules/goods/goods.service.ts 第51-79行9. 商品浏览量是如何实现的?为什么采用异步累加?回答:商品浏览量采用异步累加机制:实现方式:// 异步累加浏览量(不阻塞响应) this.goodsRepo.increment({ id: goodsId }, 'viewCount', 1).catch(() => {});为什么异步:浏览量是非关键业务数据,不需要同步等待异步处理可以提升接口响应速度即使累加失败也不影响用户查看商品详情优化考虑:高并发场景可改为Redis计数器,定时批量同步到数据库可添加防刷机制(同一用户短时间内重复访问只计一次)代码位置:back-end/src/modules/goods/goods.service.ts 第181行10. 商品搜索是如何实现的?支持哪些筛选条件?回答:商品搜索通过TypeORM的QueryBuilder实现:支持的筛选条件:分类筛选:categoryId关键词搜索:标题或描述模糊匹配价格区间:minPrice、maxPrice成色等级:condition状态筛选:status实现代码:const qb = this.goodsRepo .createQueryBuilder('goods') .where('goods.status NOT IN (6, 7)'); // 排除软删除 if (query.keyword) { qb.andWhere('(goods.title LIKE :keyword OR goods.description LIKE :keyword)', { keyword: `%${query.keyword}%` }); } if (query.minPrice != null) { qb.andWhere('goods.price >= :minPrice', { minPrice: query.minPrice }); } // ...性能优化:分页查询,默认每页20条按创建时间倒序排列关键字段建立索引代码位置:back-end/src/modules/goods/goods.service.ts 第89-147行四、交易订单模块11. 订单状态机是如何设计的?状态如何流转?回答:订单状态采用状态机模式,共7种状态:状态值状态名称说明1CREATED待付款2PAID待发货3SHIPPED待收货4COMPLETED已完成5REFUNDING退款中6REFUNDED已退款7CANCELLED已取消状态流转规则:创建订单 → 待付款(1)支付成功 → 待发货(2)卖家发货 → 待收货(3)买家确认收货 → 已完成(4)申请退款 → 退款中(5)退款成功 → 已退款(6)取消订单 → 已取消(7)代码位置:back-end/src/modules/orders/orders.service.ts 第28-43行12. 订单创建时如何保证数据一致性?回答:订单创建使用数据库事务保证数据一致性:事务处理流程:await this.ordersRepo.manager.transaction(async (em) => { // 1. 扣减库存 goods.stock -= quantity; if (goods.stock <= 0) goods.status = 2; // 已售空 await em.save(goods); // 2. 创建订单 await em.save(this.ordersRepo.create({ orderNo, buyerId, sellerId: goods.sellerId, goodsId: goods.id, price: price.toFixed(2), // ... })); });一致性保证:库存扣减和订单创建在同一事务中任一步骤失败,整个事务回滚避免超卖问题代码位置:back-end/src/modules/orders/orders.service.ts 第183-227行13. 订单超时自动取消是如何实现的?回答:系统设计了订单超时机制:超时规则:待付款订单:30分钟超时自动取消待发货订单:72小时超时提醒待收货订单:7天自动确认收货前端倒计时:if (order.status === OrderStatus.CREATED) { const expireAt = new Date(order.createTime.getTime() + 30 * 60 * 1000); const seconds = Math.max(0, Math.floor((expireAt.getTime() - now.getTime()) / 1000)); countDown = { type: 'payment', expireAt, seconds }; }后端处理:可通过定时任务扫描超时订单或使用消息队列延迟队列实现代码位置:back-end/src/modules/orders/orders.service.ts 第388-400行14. 退款流程是如何设计的?回答:退款流程设计如下:退款申请条件:已付款、已发货、已完成(7天内)的订单可申请退款已完成订单超过7天无法申请退款处理流程:买家申请退款 → 订单状态变为退款中(5)卖家同意 → 金额退回买家钱包,恢复商品库存卖家拒绝 → 填写拒绝原因,订单恢复原状态退款代码实现:async handleRefund(userId: number, orderNo: string, dto: RefundHandleDto) { if (dto.action === RefundAction.AGREE) { await this.dataSource.transaction(async (em) => { // 1. 更新订单状态 order.status = OrderStatus.REFUNDED; await em.save(order); // 2. 退款到买家钱包 buyerWallet.balance = (Number(balanceBefore) + Number(order.price)).toFixed(2); await em.save(buyerWallet); // 3. 恢复商品库存 goods.stock += order.quantity; await em.save(goods); }); } }代码位置:back-end/src/modules/orders/orders.service.ts 第708-801行五、信用评价模块15. 信用评级体系是如何设计的?有什么作用?回答:信用评级体系是本系统的创新点:信用分规则:初始值:600分分值范围:0-1000分六个等级:极好(≥900)、优秀(≥800)、良好(≥700)、一般(≥600)、较差(≥500)、极差(<500)加分规则:实名认证:+50分订单完成:+5分收到好评:+3分扣分规则:取消订单:-10分超时违约:-20分违规处理:-50分周期限制:每条规则设定每日、每周、每月的积分变化上限,防止恶意刷分作用:为买家提供卖家信用参考约束用户行为,降低交易风险信用过低可限制部分功能代码位置:back-end/src/modules/credit/credit.service.ts16. 信用分的周期限制是如何实现的?回答:周期限制通过信用历史表查询实现:private async checkPeriodLimit( userId: number, ruleCode: string, dailyLimit: number, weeklyLimit: number, monthlyLimit: number, ): Promise<boolean> { const now = new Date(); const todayStart = new Date(now); todayStart.setHours(0, 0, 0, 0); // 检查每日上限 if (dailyLimit > 0) { const dailyCount = await this.creditHistoryRepo .createQueryBuilder('ch') .where('ch.userId = :userId', { userId }) .andWhere('ch.reason LIKE :reason', { reason: `%[${ruleCode}]%` }) .andWhere('ch.changeScore > 0') .andWhere('ch.createTime >= :todayStart', { todayStart }) .getCount(); if (dailyCount >= dailyLimit) return false; } // 类似检查每周、每月上限... }代码位置:back-end/src/modules/credit/credit.service.ts 第152-207行六、钱包支付模块17. 钱包充值流程是如何设计的?回答:钱包充值采用两阶段设计:阶段1:创建充值订单校验充值渠道(支付宝/微信)校验充值限额(单次≤10000元,单日≤50000元)生成支付单号,创建payment_record记录返回支付参数给前端阶段2:支付成功回调更新payment_record状态为PAID写入wallet_transaction流水更新wallet_account余额发送充值成功通知代码位置:back-end/src/modules/wallet/wallet.service.ts 第223-315行18. 提现功能有哪些限制?如何保证资金安全?回答:提现功能设计了多重限制:金额限制:单次提现上限:5000元单日提现上限:20000元最低提现金额:10元次数限制:每日最多提现3次余额校验:可用余额必须≥提现金额账户状态:钱包账户必须为启用状态资金安全:使用数据库事务悲观锁锁定账户先冻结余额,审核通过后扣减代码位置:back-end/src/modules/wallet/wallet.service.ts 第436-561行19. 如何防止钱包并发操作导致的数据不一致?回答:系统采用悲观锁机制防止并发问题:await this.dataSource.transaction(async (em) => { // 悲观锁锁定钱包账户 const account = await em.findOne(WalletAccountEntity, { where: { userId }, lock: { mode: 'pessimistic_write' }, }); // 执行余额操作 account.balance = newBalance.toFixed(2); await em.save(account); });原理:悲观锁在读取数据时加锁,其他事务必须等待结合数据库事务,保证操作的原子性避免超扣、重复扣款等问题代码位置:back-end/src/modules/wallet/wallet.service.ts 第504-515行七、数据库设计20. 数据库表是如何设计的?核心表有哪些?回答:系统核心包含7张数据表:表名说明核心字段user用户表id, student_id, username, password, credit_score, rolegoods商品表id, seller_id, title, price, stock, status, view_countorders订单表id, order_no, buyer_id, seller_id, goods_id, status, pricewallet_account钱包账户表id, user_id, balance, frozen_balancecategory商品分类表id, name, icon, sort_ordercredit_history信用历史表id, user_id, change_score, reasoncredit_rule信用规则表id, rule_code, rule_name, score_change表关系设计:用户-商品:1:N(一个用户可发布多个商品)用户-订单:1:N(用户可作为买家或卖家参与订单)用户-钱包:1:1(每个用户拥有唯一钱包账户)商品-分类:N:1(多个商品归属于同一分类)SQL文件位置:整体架构文档/campus_secondhand.sql21. 如何优化数据库查询性能?回答:系统从多个方面优化查询性能:索引设计:主键索引:所有表的id字段唯一索引:学号、手机号、订单号外键索引:seller_id、buyer_id、goods_id查询索引:status、create_time查询优化:使用QueryBuilder构建高效查询分页查询,避免全表扫描只查询需要的字段缓存策略:Redis缓存热点数据(如分类列表)JWT Token存储在Redis异步处理:浏览量累加异步执行通知发送异步处理八、系统安全性22. 系统有哪些安全防护措施?回答:系统实现了多层次的安全防护:认证安全:JWT双令牌机制密码bcrypt加密存储Token黑名单机制授权安全:基于角色的访问控制(RBAC)接口级别权限校验(Guard)输入安全:请求参数校验(ValidationPipe)XSS过滤SQL注入防护(TypeORM参数化查询)业务安全:信用评级约束用户行为交易金额限制举报机制代码位置:参数校验:back-end/src/main.ts 第14-20行XSS过滤:back-end/src/common/utils/xss.util.ts23. 如何防止XSS攻击?回答:系统通过以下方式防止XSS攻击:输入过滤:// xss.util.ts import validator from 'validator'; export function sanitizeXss(input: string): string { return validator.escape(input); }参数校验:// main.ts app.useGlobalPipes( new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true, }), );前端转义:Vue默认对插值表达式进行HTML转义九、前端实现24. 前端状态管理是如何实现的?回答:前端使用Pinia进行状态管理:Store设计:auth.ts:用户认证状态(token、用户信息、登录状态)app.ts:应用全局状态组合式API风格:export const useAuthStore = defineStore('auth', () => { const user = ref<User | null>(null) const token = ref<string | null>(null) const isAuthenticated = computed(() => !!token.value) const login = async (params) => { /* ... */ } const logout = async () => { /* ... */ } return { user, token, isAuthenticated, login, logout } })持久化:Token和用户信息存储在localStorage代码位置:frontEnd/src/stores/auth.ts25. 前端路由守卫是如何实现的?回答:前端通过Vue Router的导航守卫实现权限控制:router.beforeEach(async (to, from, next) => { const authStore = useAuthStore() const { requiresAuth, requiresGuest, roles } = to.meta // 需要登录但未登录 if (requiresAuth && !authStore.isAuthenticated) { next('/login') return } // 登录页但已登录 if (requiresGuest && authStore.isAuthenticated) { next('/home') return } // 角色权限校验 if (roles && roles.includes('admin') && authStore.user?.role !== 2) { next('/home') return } next() })代码位置:frontEnd/src/router/index.ts 第261-285行十、项目难点与解决方案26. 项目开发过程中遇到了哪些难点?如何解决的?回答:难点1:订单状态流转的复杂性问题:订单涉及多种状态和状态转换,容易出现非法状态转换解决:采用状态机模式,定义明确的状态枚举和转换规则,每次状态变更前校验当前状态是否允许转换难点2:并发场景下的数据一致性问题:钱包余额操作、库存扣减等场景存在并发风险解决:使用数据库事务+悲观锁机制,保证操作的原子性难点3:信用评级体系的规则配置问题:信用规则需要灵活配置,同时要防止恶意刷分解决:设计信用规则配置表,支持动态配置;实现周期限制检查机制难点4:前后端Token同步问题:Token过期后需要无感刷新解决:实现Token刷新机制,前端拦截401错误自动刷新Token27. 系统还有哪些可以改进的地方?回答:实时通讯:新增买卖双方实时在线沟通功能,降低沟通成本智能推荐:基于用户浏览历史、校园身份标签进行商品推荐搜索优化:引入Elasticsearch实现全文搜索,提升搜索体验消息推送:集成WebSocket实现实时消息推送安全升级:敏感数据加密存储,引入风控系统性能优化:引入消息队列处理异步任务,提升系统吞吐量十一、代码定位速查28. 如果老师问某个功能的代码在哪里,如何快速定位?回答:请参考本文档附件《后端代码架构快速定位指南》,按照以下方式快速定位:按模块定位:根据功能所属模块,在back-end/src/modules/下找到对应目录按层次定位:控制器(Controller):处理HTTP请求服务(Service):处理业务逻辑实体(Entity):数据库表映射按功能定位:登录注册 → modules/auth/商品管理 → modules/goods/订单交易 → modules/orders/钱包支付 → modules/wallet/信用评价 → modules/credit/后台管理 → modules/admin/附件:后端代码架构快速定位指南一、项目目录结构总览back-end/ ├── src/ │ ├── common/ # 公共模块 │ │ ├── constants/ # 常量定义(错误码) │ │ ├── decorators/ # 自定义装饰器 │ │ ├── exceptions/ # 自定义异常 │ │ ├── filters/ # 异常过滤器 │ │ ├── interceptors/ # 拦截器 │ │ ├── types/ # 类型定义 │ │ └── utils/ # 工具函数 │ │ │ ├── infrastructure/ # 基础设施 │ │ ├── database/ # 数据库模块 │ │ ├── logger/ # 日志配置 │ │ └── redis/ # Redis模块 │ │ │ ├── modules/ # 业务模块(核心) │ │ ├── admin/ # 后台管理模块 │ │ ├── auth/ # 认证授权模块 │ │ ├── categories/ # 商品分类模块 │ │ ├── comments/ # 评论模块 │ │ ├── credit/ # 信用评价模块 │ │ ├── goods/ # 商品资源模块 │ │ ├── notifications/ # 消息通知模块 │ │ ├── orders/ # 交易订单模块 │ │ ├── payments/ # 支付模块 │ │ ├── users/ # 用户信息模块 │ │ └── wallet/ # 电子钱包模块 │ │ │ ├── app.module.ts # 应用主模块 │ ├── app.controller.ts # 应用控制器 │ ├── app.service.ts # 应用服务 │ └── main.ts # 应用入口 │ ├── dist/ # 编译输出目录 ├── coverage/ # 测试覆盖率报告 ├── .env # 环境变量配置 └── package.json # 项目依赖配置二、核心模块详细定位1. 认证授权模块 (auth)目录: src/modules/auth/文件功能关键代码位置auth.controller.ts认证接口控制器登录、注册、刷新Token、登出接口auth.service.ts认证业务逻辑密码加密、Token签发、黑名单机制jwt.strategy.tsJWT策略Token验证策略jwt-auth.guard.tsJWT守卫接口权限拦截dto/auth.dto.ts数据传输对象登录、注册参数定义关键功能代码定位:用户注册:auth.service.ts → register() 方法(第40-96行)用户登录:auth.service.ts → login() 方法(第109-164行)Token刷新:auth.service.ts → refresh() 方法(第176-224行)用户登出:auth.service.ts → logout() 方法(第234-244行)2. 商品资源模块 (goods)目录: src/modules/goods/文件功能关键代码位置goods.controller.ts商品接口控制器商品CRUD接口goods.service.ts商品业务逻辑发布、查询、上下架、举报goods.entity.ts商品实体数据库表映射report.entity.ts举报实体举报记录表映射dto/goods.dto.ts数据传输对象商品参数定义关键功能代码定位:商品列表:goods.service.ts → list() 方法(第89-147行)商品详情:goods.service.ts → detail() 方法(第156-227行)发布商品:goods.service.ts → create() 方法(第236-270行)更新商品:goods.service.ts → update() 方法(第280-319行)删除商品:goods.service.ts → delete() 方法(第329-367行)下架商品:goods.service.ts → offline() 方法(第379-418行)举报商品:goods.service.ts → report() 方法(第520-560行)商品状态说明:// 状态枚举(goods.service.ts 第24-32行) // 0: 待审核 (PENDING_AUDIT) // 1: 在售 (ONLINE) // 2: 已售空 (SOLD) // 3: 下架 (OFFLINE) // 4: 审核拒绝 (REJECTED) // 5: 违规商品 (VIOLATION) // 6: 软删除 (DELETED)3. 交易订单模块 (orders)目录: src/modules/orders/文件功能关键代码位置orders.controller.ts订单接口控制器订单CRUD接口orders.service.ts订单业务逻辑创建、支付、发货、确认、退款order.entity.ts订单实体数据库表映射dto/orders.dto.ts数据传输对象订单参数定义关键功能代码定位:创建订单:orders.service.ts → createOrder() 方法(第148-241行)订单列表:orders.service.ts → listOrders() 方法(第250-324行)订单详情:orders.service.ts → detail() 方法(第369-443行)取消订单:orders.service.ts → cancel() 方法(第454-491行)订单发货:orders.service.ts → ship() 方法(第502-533行)确认收货:orders.service.ts → confirm() 方法(第544-642行)申请退款:orders.service.ts → refund() 方法(第653-695行)处理退款:orders.service.ts → handleRefund() 方法(第708-801行)订单状态说明:// 状态枚举(orders.service.ts 第28-43行) export enum OrderStatus { CREATED = 1, // 待付款 PAID = 2, // 待发货 SHIPPED = 3, // 待收货 COMPLETED = 4, // 已完成 REFUNDING = 5, // 退款中 REFUNDED = 6, // 已退款 CANCELLED = 7, // 已取消 }4. 电子钱包模块 (wallet)目录: src/modules/wallet/文件功能关键代码位置wallet.controller.ts钱包接口控制器余额查询、充值、提现接口wallet.service.ts钱包业务逻辑充值、提现、交易流水wallet-account.entity.ts钱包账户实体账户表映射wallet-transaction.entity.ts交易流水实体流水表映射dto/wallet.dto.ts数据传输对象钱包参数定义关键功能代码定位:钱包信息:wallet.service.ts → getMe() 方法(第114-128行)交易流水:wallet.service.ts → getTransactions() 方法(第140-181行)充值功能:wallet.service.ts → topup() 方法(第223-315行)提现功能:wallet.service.ts → withdraw() 方法(第436-561行)交易类型说明:// 交易类型(wallet.service.ts 第16-21行) // TOPUP: 充值 // ORDER_PAY: 订单支付 // ORDER_REFUND: 订单退款 // WITHDRAW: 提现 // ADMIN_ADJUST: 管理员调账5. 信用评价模块 (credit)目录: src/modules/credit/文件功能关键代码位置credit.service.ts信用业务逻辑加分、扣分、周期限制检查credit.module.ts信用模块定义模块配置关键功能代码定位:增加信用分:credit.service.ts → addCredit() 方法(第39-94行)扣减信用分:credit.service.ts → deductCredit() 方法(第104-140行)周期限制检查:credit.service.ts → checkPeriodLimit() 方法(第152-207行)按规则加分:credit.service.ts → addCreditByRule() 方法(第216-235行)按规则扣分:credit.service.ts → deductCreditByRule() 方法(第244-262行)信用等级:credit.service.ts → getCreditLevel() 方法(第269-283行)信用等级划分:// 信用等级(credit.service.ts 第269-283行) // ≥900: 极好 (excellent) // ≥800: 优秀 (great) // ≥700: 良好 (good) // ≥600: 一般 (normal) // ≥500: 较差 (poor) // <500: 极差 (bad)6. 用户信息模块 (users)目录: src/modules/users/文件功能关键代码位置users.controller.ts用户接口控制器用户信息接口users.service.ts用户业务逻辑用户信息管理、实名认证user.entity.ts用户实体用户表映射credit-history.entity.ts信用历史实体信用历史表映射receiver.entity.ts收货地址实体地址表映射dto/users.dto.ts数据传输对象用户参数定义7. 后台管理模块 (admin)目录: src/modules/admin/子目录/文件功能admin.controller.ts后台管理控制器admin.service.ts后台管理服务admin.guard.ts管理员权限守卫users/用户管理goods/商品审核管理orders/订单管理reports/举报管理credit/信用规则管理categories/分类管理stats/数据统计audit-log.entity.ts审计日志实体三、公共模块定位1. 错误码定义文件: src/common/constants/error-codes.ts// 错误码分段 // 1000-1999: 认证与授权 // 2000-2999: 用户域 // 3000-3999: 商品域 // 4000-4999: 订单域 // 5000-5999: 支付域 // 8000-8999: 钱包域 // 9000-9999: 系统与基础设施2. 异常过滤器文件: src/common/filters/api-exception.filter.ts功能:统一捕获和处理异常,返回标准格式的错误响应3. 响应拦截器文件: src/common/interceptors/api-response.interceptor.ts功能:统一包装响应数据,返回标准格式{ code, message, data }四、快速定位技巧1. 按功能关键词搜索在IDE中使用全局搜索功能,搜索以下关键词:接口路径:如/auth/login、/goods、/orders方法名:如createOrder、handleRefund、addCredit状态值:如OrderStatus.CREATED、GoodsStatus.ONLINE2. 按API路径定位在控制器文件中搜索路由装饰器找到对应的处理方法跟踪到服务层查看业务逻辑3. 按数据库表定位在实体文件中查看表结构定义在服务文件中查看对该表的操作五、前端代码定位1. 目录结构frontEnd/src/ ├── api/ # API接口定义 ├── components/ # 公共组件 ├── hooks/ # 自定义Hook ├── router/ # 路由配置 ├── stores/ # 状态管理 ├── styles/ # 样式文件 ├── types/ # 类型定义 ├── utils/ # 工具函数 └── views/ # 页面组件 ├── admin/ # 后台管理页面 ├── auth/ # 登录注册页面 ├── goods/ # 商品详情页面 ├── order/ # 订单页面 ├── publish/ # 发布页面 └── user/ # 用户中心页面2. 关键文件定位功能文件位置路由配置router/index.ts认证状态stores/auth.tsAPI请求封装utils/request.ts登录页面views/auth/Login.vue商品详情views/goods/GoodsDetail.vue订单列表views/order/OrderList.vue后台管理views/admin/文档版本: v1.0 更新日期: 2026年3月 适用项目: 基于Node+Vue融合信用评级的校园二手交易系统
2026年03月27日
24 阅读
0 评论
0 点赞
1
...
5
6
7
8