【面试】Java 记录一次面试过程 三年工作经验
2025
个人工作经验与基础概念
- 工作挑战及解决方式:这需要根据个人实际工作经历来回答,例如在项目中遇到性能瓶颈,通过代码优化、数据库索引调整或引入缓存机制等方式解决。
- 单例模式:
- 常见的实现方式有饿汉式、懒汉式(线程安全与不安全版本)、双重检查锁定、静态内部类等。
- 例如饿汉式:
public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
破坏单例的方式:
反射:通过反射获取单例类的构造函数并创建新实例,如:
Class clazz = Singleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton newInstance = (Singleton) constructor.newInstance();
反序列化:如果单例类实现了 Serializable
接口,在反序列化时会创建新对象。
可通过重写 readResolve
方法来防止,如:
private Object readResolve() {return instance;
}
对象克隆:若单例类未重写 clone
方法或重写不当,可能被克隆出多个实例。
应重写 clone
方法并抛出 CloneNotSupportedException
。
- JDK 相关知识:
JDK
(Java Development Kit)是 Java 开发工具包,包含 JRE(Java Runtime Environment)和一系列开发工具。JRE
则是运行 Java 程序的最小环境,包括 Java 虚拟机(JVM)、Java 核心类库等。JIT
(即时编译器)原理:Java 程序最初由 JVM 中的解释器执行,随着程序运行,JIT 编译器会将热点代码(频繁执行的代码段)编译成机器码,存储在代码缓存中,后续执行时直接执行编译后的机器码,提高执行效率。它会进行诸如方法内联、逃逸分析等优化操作,减少方法调用开销和对象创建开销等。
- Java 线程基础:
线程定义:线程是进程内的一个执行单元,一个进程可以包含多个线程,它们共享进程的资源(如内存空间),但有自己的程序计数器、栈等。
实现方式:
继承 Thread
类:
public class MyThread extends Thread {@Overridepublic void run() {// 线程执行的逻辑}
}
// 使用:MyThread thread = new MyThread(); thread.start();
实现 Runnable
接口:
public class MyRunnable implements Runnable {@Overridepublic void run() {// 线程执行的逻辑}
}
// 使用:Thread thread = new Thread(new MyRunnable()); thread.start();
线程池参数含义:
- 核心线程数(corePoolSize):线程池长期维持的最小线程数量,即使线程处于空闲状态也不会被回收。
- 最大线程数(maximumPoolSize):线程池允许创建的最大线程数量。
- 队列容量(workQueue):用于存放等待执行任务的阻塞队列,如 ArrayBlockingQueue
、LinkedBlockingQueue
等。
- 线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数时,空闲线程的存活时间。
拒绝策略:
- AbortPolicy
:直接抛出 RejectedExecutionException
异常,阻止任务提交。
- CallerRunsPolicy
:由调用者线程(提交任务的线程)执行被拒绝的任务。
- DiscardPolicy
:直接丢弃被拒绝的任务,不做任何处理。
- DiscardOldestPolicy
:丢弃阻塞队列中最老的任务(即最早进入队列的任务),然后重新尝试提交当前任务。
线程状态及切换:
- 新建(New):创建线程对象后但尚未调用 start
方法的状态。
- 就绪(Runnable):线程对象调用 start
方法后,等待 CPU 资源分配的状态。
- 运行(Running):线程获得 CPU 资源,正在执行 run
方法的状态。
- 阻塞(Blocked):线程因等待锁、等待 I/O 操作完成等原因暂停执行,进入阻塞状态,例如 synchronized
块获取锁失败时。
- 死亡(Terminated):线程执行完 run
方法或因异常退出后的状态。
-
- 切换条件:
- 就绪到运行:线程调度器从就绪队列中选择一个线程分配 CPU 资源。
- 运行到阻塞:如遇到 synchronized
锁未获取到、wait
方法调用、I/O 操作等。
- 阻塞到就绪:如锁被释放、I/O 操作完成、notify
或 notifyAll
方法被调用使线程从 wait
状态唤醒等。
- 运行到死亡:线程正常执行完 run
方法或因未捕获的异常导致线程终止。
引用类型与 ThreadLocal
- 字符串与引用类型:
String
:是不可变的字符序列,其对象一旦创建就不能被修改。它适用于字符串常量操作,因为不可变性保证了字符串在多线程环境下的安全性,并且可以被共享。例如:String str = "hello";
。StringBuilder
:是可变的字符序列,非线程安全,适用于单线程下频繁修改字符串的场景,如字符串拼接操作,其性能比String
频繁拼接要高。例如:
StringBuilder sb = new StringBuilder();
sb.append("hello");
sb.append(" world");
String result = sb.toString();
StringBuffer
:也是可变的字符序列,但它是线程安全的,适用于多线程环境下的字符串操作,但由于加锁机制,性能相对 StringBuilder
稍低。例如:
StringBuffer sbf = new StringBuffer();
sbf.append("hello");
sbf.append(" world");
String result = sbf.toString();
String
的常量池特性:Java 为了提高字符串的使用效率,维护了一个字符串常量池。当创建字符串字面量时,JVM 会首先在常量池中查找是否存在相同内容的字符串,如果存在则直接返回该字符串的引用,否则在常量池中创建新的字符串对象并返回引用。
例如:String str1 = "hello"; String str2 = "hello";
这里 str1
和 str2
可能指向常量池中的同一个字符串对象。
String
类不可变的原因:
- 安全性:不可变字符串可以在多线程环境下安全地共享,避免了因字符串修改导致的并发问题。
- 哈希码缓存:字符串的哈希码在其生命周期内不会改变,这使得它可以在哈希相关的集合(如HashMap
)中高效地使用,因为不需要重新计算哈希码。
- 字符串常量池:不可变性使得字符串可以被缓存和复用,提高了内存使用效率。
引用类型区别与场景:
强引用:是最常见的引用类型,如 Object obj = new Object();
中的 obj
就是强引用。只要强引用存在,对象就不会被垃圾回收。
软引用:用于描述一些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。例如在缓存场景中,当内存紧张时可以回收软引用指向的缓存对象。
SoftReference<Object> softRef = new SoftReference<>(new Object());
弱引用:其生命周期更短,无论内存是否充足,只要垃圾回收器运行,弱引用指向的对象就会被回收。常用于解决内存泄漏问题,如在哈希表中,如果键使用弱引用,当对象没有其他强引用时,键可以被回收,避免哈希表中积累无用的键值对。
WeakReference<Object> weakRef = new WeakReference<>(new Object());
虚引用:也称为幽灵引用或幻影引用,是最弱的一种引用关系。无法通过虚引用获取对象实例,主要用于跟踪对象被垃圾回收的状态,在对象被回收时会收到一个系统通知。通常与引用队列结合使用,如:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
ThreadLocal 原理:ThreadLocal
用于在多线程环境下为每个线程提供独立的变量副本。其原理是每个 Thread
对象内部都有一个 ThreadLocalMap
,ThreadLocal
作为键,对应的值就是线程本地变量。当在一个线程中通过 ThreadLocal
设置变量时,实际上是将变量存储在该线程的 ThreadLocalMap
中。例如:
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(1); // 在当前线程中设置值
Integer value = threadLocal.get(); // 获取当前线程中的值
在 ThreadLocal
的 set
方法中,首先获取当前线程,然后获取线程的 ThreadLocalMap
,如果 Map
不存在则创建,接着将 ThreadLocal
实例作为键,要设置的值作为值存入 Map
中。get
方法类似,先获取当前线程的 ThreadLocalMap
,再根据 ThreadLocal
实例作为键获取对应的值。
Spring 框架相关
- Spring Boot 与 Spring 对比:
- Spring Boot 存在的必要性:
- 简化配置:Spring 配置较为繁琐,需要大量的 XML 或 Java 配置文件来定义 bean、数据源、事务等。Spring Boot 采用约定优于配置的原则,通过自动配置机制,根据项目依赖和默认配置,自动为项目进行大量的基础配置,减少了开发者的配置工作量。例如,引入
spring-web
依赖后,Spring Boot 会自动配置一个嵌入式的 Tomcat 服务器,并设置好相关的端口、上下文路径等。 - 快速启动:Spring Boot 提供了方便的项目启动器(starters),开发者可以通过选择不同的启动器快速搭建项目框架,并且可以通过命令行或配置文件方便地修改一些常见的配置参数,如端口号、数据库连接信息等,大大提高了项目的开发和启动效率。
- 集成度高:Spring Boot 集成了很多常用的第三方库和框架,如数据库连接池(如 HikariCP)、日志框架(如 Logback)等,并提供了统一的配置方式,使得在项目中使用这些库更加方便。
- 简化配置:Spring 配置较为繁琐,需要大量的 XML 或 Java 配置文件来定义 bean、数据源、事务等。Spring Boot 采用约定优于配置的原则,通过自动配置机制,根据项目依赖和默认配置,自动为项目进行大量的基础配置,减少了开发者的配置工作量。例如,引入
- Spring Boot 存在的必要性:
- Spring AOP:
- 使用场景:
- 日志记录:在方法执行前后记录日志,方便排查问题和监控系统运行状态。例如,在业务方法执行前记录方法入参和调用时间,执行后记录返回结果和执行时长。
- 权限验证:在方法调用前检查用户是否具有执行该方法的权限,如在控制器方法中验证用户是否已登录且具有相应的操作权限。
- 事务管理:通过 AOP 实现事务的自动开启、提交和回滚,确保数据库操作的一致性。例如,在一个业务方法中包含多个数据库操作,AOP 可以将这些操作包裹在一个事务中,如果其中某个操作出现异常,事务会自动回滚,保证数据的完整性。
- 实现原理:Spring AOP 主要基于动态代理机制。
- 对于实现了接口的目标对象,Spring 默认使用 JDK 动态代理。JDK 动态代理通过
java.lang.reflect.Proxy
类和InvocationHandler
接口实现。在运行时,JDK 动态代理会创建一个实现目标对象接口的代理类,代理类的每个方法调用都会被转发到InvocationHandler
的invoke
方法中,在invoke
方法中可以在目标方法执行前后添加额外的逻辑,如日志记录、权限验证等。 - 对于没有实现接口的目标对象,Spring 会使用 CGLIB 动态代理。CGLIB 动态代理是通过继承目标对象来实现的,在子类中重写目标对象的方法,并在重写的方法中添加额外的逻辑。CGLIB 动态代理的性能相对 JDK 动态代理可能稍低,但可以代理没有实现接口的类。
- 对于实现了接口的目标对象,Spring 默认使用 JDK 动态代理。JDK 动态代理通过
- 使用场景:
- Spring Boot 自动化配置:
- 过程:Spring Boot 启动时,会从类路径下查找
META-INF/spring.factories
文件,该文件中配置了一系列的自动配置类。这些自动配置类会根据项目的依赖和配置情况,自动为项目配置各种组件。例如,如果项目中引入了spring-boot-starter-web
依赖,WebMvcAutoConfiguration
类会自动配置 Spring MVC 的相关组件,如DispatcherServlet
、视图解析器等。在自动配置过程中,会通过条件注解(如@ConditionalOnClass
、@ConditionalOnMissingBean
等)来判断是否需要进行某项配置。如果满足条件,就会创建相应的 bean 并注入到 Spring 容器中。同时,开发者也可以通过在自己的配置类中使用@EnableAutoConfiguration
注解或在application.properties
/application.yml
文件中设置相关属性来定制自动配置的行为。
- 过程:Spring Boot 启动时,会从类路径下查找
- Spring 事务管理:
- 事务传播机制:
REQUIRED
:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认的传播行为。例如,在一个业务方法 A 中调用了另一个业务方法 B,且方法 A 已经开启了事务,如果方法 B 的事务传播机制是REQUIRED
,那么方法 B 会加入方法 A 的事务中。REQUIRES_NEW
:无论当前是否存在事务,都会创建一个新的事务。如果当前存在事务,会将当前事务挂起,等新事务执行完毕后再恢复原事务。例如,在一个事务方法中调用另一个事务方法,且后者的传播机制是REQUIRES_NEW
,那么后者会独立于前者执行自己的事务,即使前者出现异常回滚,后者的事务也可能正常提交。NESTED
:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。嵌套事务是外层事务的一个子事务,外层事务回滚时,嵌套事务也会回滚,但嵌套事务可以独立提交或回滚。例如,在一个事务方法中调用另一个事务方法,且后者的传播机制是NESTED
,如果后者出现异常回滚,不会影响外层事务的其他操作,除非外层事务也出现异常。
- 事务失效的场景:
- 方法非
public
:Spring 事务管理默认只对public
方法生效,因为事务的代理是基于接口或类的代理,如果方法不是public
,则无法通过代理拦截到方法调用,从而导致事务无法生效。 - 异常被捕获但未抛出:Spring 事务是通过异常来触发回滚的,如果在事务方法中捕获了异常但没有抛出,事务管理器无法感知到异常,就不会进行回滚操作。例如,在一个事务方法中使用
try-catch
块捕获了异常,但没有在catch
块中抛出RuntimeException
或Error
类型的异常,事务将不会回滚。 - 事务方法嵌套调用:如果在一个事务方法内部直接调用另一个事务方法(不是通过代理调用),可能会导致事务失效。因为直接调用不会经过事务代理,无法应用事务的相关逻辑。例如,在一个
@Transactional
注解的方法 A 中,通过this
关键字调用了另一个同样有@Transactional
注解的方法 B,方法 B 的事务可能不会生效。
- 方法非
- 事务传播机制:
MySQL 数据库知识
- 事务与存储引擎:
- ACID 特性:
- 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的所有操作要么全部成功执行,要么全部回滚。例如,在银行转账事务中,从账户 A 转出 100 元到账户 B,这两个操作必须作为一个整体,要么都成功完成,要么都不执行,不能出现只转出而未转入或反之的情况。如果转账过程中出现错误,如账户余额不足或系统故障,整个事务会回滚,账户 A 和账户 B 的余额都不会改变。
- 一致性(Consistency):事务执行前后,数据库的完整性约束没有被破坏。例如,在数据库中有一个表的字段设置了非空约束,事务执行过程中不会插入或更新该字段为
null
值,保证数据始终符合预先定义的规则。再比如,在一个涉及多个表的事务中,如果存在外键约束,更新或插入数据时必须满足这些约束关系,以确保数据的一致性。 - 隔离性(Isolation):多个事务并发执行时,一个事务的执行不能被其他事务干扰,各个事务之间相互隔离。例如,有两个事务同时对同一张表进行操作,一个事务在读取数据时,另一个事务对相同数据的修改不会影响到当前事务的读取结果,直到当前事务提交或回滚后,才能看到其他事务的修改。MySQL 提供了不同的隔离级别来控制事务之间的隔离程度,如读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable),不同隔离级别在并发性能和数据一致性上有不同的权衡。
- 持久性(Durability):一旦事务提交,其对数据库的修改将永久保存,即使系统发生故障也不会丢失。例如,事务提交后,数据会被持久化到磁盘等存储介质中,即使数据库服务器突然断电或崩溃,重启后数据仍然保持提交后的状态。MySQL 通过事务日志(如 redo log 和 binlog)来保证持久性,在系统恢复时可以根据这些日志重新执行已提交的事务,将数据恢复到一致状态。
- 存储引擎种类及 InnoDB 使用 B+树索引原因:
- 存储引擎种类:MySQL 常见的存储引擎有 InnoDB、MyISAM、Memory 等。InnoDB 是 MySQL 5.5 及以后版本的默认存储引擎,支持事务、行级锁和外键约束等特性,适用于大多数 OLTP(联机事务处理)应用场景。MyISAM 不支持事务和行级锁,但具有简单、高效的特点,适用于一些对事务要求不高的读密集型应用,如数据仓库、日志记录等。Memory 存储引擎将数据存储在内存中,读写速度非常快,但数据在服务器关闭后会丢失,通常用于临时表或对性能要求极高的特定场景。
- InnoDB 使用 B+树索引原因:
- 磁盘 I/O 优化:B+树的非叶子节点只存储索引键值,不存储数据记录,相比 B 树,相同数量的索引数据可以存储在更少的磁盘块中,使得树的高度更低。在查询数据时,减少了磁盘 I/O 次数,提高了查询效率。例如,对于一个有大量数据的表,使用 B+树索引可以更快地定位到数据所在的磁盘块。
- 数据存储结构适配:InnoDB 采用聚簇索引(clustered index)的方式存储数据,数据行与索引键值紧密相连,按照索引顺序存储在磁盘上。B+树的叶子节点形成一个有序链表,便于范围查询(如
BETWEEN
操作)和全表扫描,能够很好地满足数据库的查询需求。例如,在执行SELECT * FROM table WHERE column BETWEEN value1 AND value2
这样的查询语句时,B+树可以高效地遍历叶子节点获取满足条件的数据。 - 查询性能提升:B+树的叶子节点存储了所有的索引键值和对应的数据行指针,在进行等值查询和范围查询时,都可以在叶子节点上快速定位到数据,而不需要像 B 树那样可能需要回溯到非叶子节点进行进一步查找。例如,在查询一个特定值或一个范围内的值时,B+树的结构使得查询可以直接在叶子节点上进行,减少了查找路径和时间。
- B 树与 B+树索引结构差异:
- 节点存储内容:B 树的每个节点都存储索引键值和对应的数据记录(如果是叶子节点)或指向子节点的指针(如果是非叶子节点)。而 B+树的非叶子节点只存储索引键值和指向子节点的指针,叶子节点存储索引键值和对应的数据行指针(在 InnoDB 中)或数据记录(在某些其他存储引擎中)。例如,在一个 B 树索引中,非叶子节点可能同时存储了部分数据,而 B+树的非叶子节点则更专注于索引键值的索引功能,数据存储在叶子节点,使得 B+树的非叶子节点可以存储更多的索引键值,降低树的高度。
- 叶子节点连接方式:B 树的叶子节点之间没有直接的连接关系,而 B+树的叶子节点通过指针形成一个双向链表。这种连接方式使得 B+树在进行范围查询时可以方便地遍历叶子节点,提高了范围查询的效率。例如,在执行一个范围查询时,B+树可以从起始键值所在的叶子节点开始,通过链表依次访问后续的叶子节点,直到满足查询条件结束,而 B 树可能需要多次从根节点开始查找,效率相对较低。
- ACID 特性:
- 索引相关知识:
- 聚集索引和非聚集索引区别:
- 聚集索引:数据行的物理存储顺序与索引键值的顺序一致。在 InnoDB 中,表的主键会自动成为聚集索引,如果没有显式指定主键,InnoDB 会选择一个唯一非空索引作为聚集索引,如果没有这样的索引,InnoDB 会隐式创建一个 6 字节的行 ID 作为聚集索引。聚集索引的叶子节点存储了实际的数据行,因此通过聚集索引进行查询时,可以直接获取到数据,速度较快。例如,对于一个以用户 ID 为主键的用户表,按照用户 ID 进行排序存储,当查询用户 ID 为特定值的用户信息时,可以通过聚集索引快速定位到对应的磁盘块并获取数据。
- 非聚集索引:数据行的物理存储顺序与索引键值的顺序无关。非聚集索引的叶子节点存储的是索引键值和对应的聚集索引键值(在 InnoDB 中)或数据行指针(在其他一些存储引擎中)。在查询数据时,如果使用非聚集索引,首先通过非聚集索引找到对应的聚集索引键值或数据行指针,然后再通过聚集索引或指针去获取完整的数据行,这个过程称为回表操作。例如,在一个用户表中,如果有一个以用户姓名为列创建的非聚集索引,当查询用户姓名为特定值的用户信息时,先通过非聚集索引找到对应的用户 ID(假设用户 ID 是聚集索引键值),然后再通过用户 ID 去聚集索引中获取完整的用户信息。
- 回表操作原理及避免方法:
- 回表操作原理:当使用非聚集索引进行查询时,如果查询的列不在非聚集索引中,数据库需要根据非聚集索引的叶子节点存储的信息(如聚集索引键值)回到聚集索引中查找完整的数据行,这个过程就是回表操作。例如,在一个包含用户 ID、用户姓名、用户年龄等列的用户表中,如果创建了一个以用户姓名为索引的非聚集索引,当执行
SELECT * FROM users WHERE name = 'John'
这样的查询语句时,首先通过用户姓名的非聚集索引找到对应的用户 ID,然后再根据用户 ID 去聚集索引中获取用户的其他信息(如年龄等),这个从非聚集索引到聚集索引查找数据的过程就是回表操作。回表操作会增加磁盘 I/O 次数,降低查询效率,尤其是在查询结果集较大时影响更为明显。 - 避免回表方法:合理创建联合索引是避免回表的一种有效方法。例如,如果经常需要查询用户姓名和年龄的信息,可以创建一个包含用户姓名和年龄的联合索引。这样,在查询时,如果查询条件只涉及用户姓名和年龄,就可以直接从联合索引中获取所需数据,而不需要回表操作。另外,在查询语句中只选择需要的列,避免使用
SELECT *
,也可以减少回表操作的可能性。例如,如果只需要查询用户姓名和用户 ID,而不需要其他列的信息,在查询语句中明确指定这两列,数据库可以直接从索引中获取这些数据,而不需要回表获取其他不必要的列。
- 回表操作原理:当使用非聚集索引进行查询时,如果查询的列不在非聚集索引中,数据库需要根据非聚集索引的叶子节点存储的信息(如聚集索引键值)回到聚集索引中查找完整的数据行,这个过程就是回表操作。例如,在一个包含用户 ID、用户姓名、用户年龄等列的用户表中,如果创建了一个以用户姓名为索引的非聚集索引,当执行
- 公司使用索引的原则:
- 避免在索引列上使用函数:如果在索引列上使用函数,数据库可能无法使用索引进行优化。例如,在一个存储日期的列上创建了索引,如果查询语句使用
WHERE YEAR(date_column) = 2023
这样的条件,数据库可能不会使用该索引,因为对索引列应用了函数。正确的做法是将日期范围作为条件进行查询,如WHERE date_column BETWEEN '2023-01-01' AND '2023-12-31'
,这样数据库可以利用索引快速定位到满足条件的数据。 - 避免使用 LIKE ‘%…%’ 模糊查询:这种模糊查询方式在大多数情况下会导致数据库进行全表扫描,无法利用索引。例如,执行
SELECT * FROM table WHERE column LIKE '%keyword%'
这样的查询语句时,数据库需要遍历整个表来查找包含关键字的记录。如果确实需要进行模糊查询,可以考虑使用 LIKE ‘keyword%’ 这样的前缀匹配方式,在某些情况下数据库可以利用索引进行优化。例如,SELECT * FROM table WHERE column LIKE 'John%'
,如果在 column 列上有合适的索引,数据库可以通过索引快速定位到以 ‘John’ 开头的记录。
- 避免在索引列上使用函数:如果在索引列上使用函数,数据库可能无法使用索引进行优化。例如,在一个存储日期的列上创建了索引,如果查询语句使用
- 聚集索引和非聚集索引区别:
项目设计与架构
- 权限系统设计:
- 数据库表结构及关系:
- 用户表:通常包含用户 ID、用户名、密码、用户状态(如启用/禁用)等字段。用户 ID 作为主键,用于唯一标识每个用户。例如,在一个简单的用户表中,可能有如下结构:
- 数据库表结构及关系:
CREATE TABLE users (user_id INT PRIMARY KEY,username VARCHAR(50) NOT NULL,password VARCHAR(100) NOT NULL,status ENUM('enabled', 'disabled') NOT NULL DEFAULT 'enabled'
);
角色表:包含角色 ID、角色名称等字段,角色 ID 为主键。例如:
CREATE TABLE roles (role_id INT PRIMARY KEY,role_name VARCHAR(50) NOT NULL
);
权限表:存储权限信息,如权限 ID、权限名称、权限描述等,权限 ID 为主键。例如:
CREATE TABLE permissions (permission_id INT PRIMARY KEY,permission_name VARCHAR(50) NOT NULL,permission_description VARCHAR(200)
);
用户 - 角色关联表:用于建立用户和角色之间的多对多关系,通常包含用户 ID 和角色 ID 两个外键,共同构成联合主键。例如:
CREATE TABLE user_roles (user_id INT NOT NULL,role_id INT NOT NULL,PRIMARY KEY (user_id, role_id),FOREIGN KEY (user_id) REFERENCES users(user_id),FOREIGN KEY (role_id) REFERENCES roles(role_id)
);
角色 - 权限关联表:建立角色和权限之间的多对多关系,包含角色 ID 和权限 ID 两个外键,共同构成联合主键。例如:
CREATE TABLE role_permissions (role_id INT NOT NULL,permission_id INT NOT NULL,PRIMARY KEY (role_id, permission_id),FOREIGN KEY (role_id) REFERENCES roles(role_id),FOREIGN KEY (permission_id) REFERENCES permissions(permission_id)
);
资源存储位置:资源信息(如菜单、功能模块等)可以存储在单独的资源表中,资源表包含资源 ID、资源名称、资源 URL 等字段,资源 ID 为主键。然后通过权限 - 资源关联表建立权限和资源之间的关系,该表包含权限 ID 和资源 ID 两个外键,共同构成联合主键。例如:
CREATE TABLE resources (resource_id INT PRIMARY KEY,resource_name VARCHAR(50) NOT NULL,resource_url VARCHAR(200) NOT NULL
);CREATE TABLE permission_resources (permission_id INT NOT NULL,resource_id INT NOT NULL,PRIMARY KEY (permission_id, resource_id),FOREIGN KEY (permission_id) REFERENCES permissions(permission_id),FOREIGN KEY (resource_id) REFERENCES resources(resource_id)
);
- 单点系统实现:
- 实现思路:单点登录(SSO)系统的实现通常基于共享认证机制。可以采用基于令牌(Token)的方式,用户在一个系统登录成功后,认证服务器会生成一个加密的令牌,并将其返回给用户。用户在访问其他系统时,携带这个令牌,其他系统通过验证令牌的有效性来确认用户的身份,而无需用户再次登录。例如,在一个企业内部有系统 A 和系统 B,用户在系统 A 登录后,系统 A 向认证服务器发送登录请求,认证服务器验证用户身份后生成一个包含用户信息和有效期的令牌(如 JWT - JSON Web Token)并返回给系统 A,系统 A 将令牌存储在用户的浏览器 Cookie 或本地存储中。当用户访问系统 B 时,系统 B 从用户请求中获取令牌,并向认证服务器发送验证请求,认证服务器验证令牌合法后,系统 B 允许用户访问,实现单点登录。
- 会话管理:
- Session 共享:可以使用集中式存储(如 Redis)来存储用户会话信息。当用户在一个系统登录成功后,将会话信息存储到 Redis 中,在其他系统需要验证用户身份时,从 Redis 中获取会话信息进行验证。例如,在一个基于 Spring Boot 的应用集群中,多个实例可以共享 Redis 中的会话数据。在用户登录系统 A 时,系统 A 将用户的会话信息(如用户 ID、登录时间、权限信息等)以键值对的形式存储到 Redis 中,键可以是用户的唯一标识(如用户 ID 或令牌)。当用户访问系统 B 时,系统 B 从请求中获取用户标识,然后从 Redis 中查询对应的会话信息,根据会话信息判断用户是否已登录和具有相应权限。
- 会话一致性维护:为了保证在分布式环境下会话的一致性,可以采用会话粘性(Session Affinity)或分布式会话管理技术。会话粘性是指将用户的请求始终路由到处理其初始登录请求的服务器实例上,这样可以保证该服务器实例上的会话信息是最新的。例如,在负载均衡器上配置会话粘性,根据用户的某个标识(如 IP 地址或 Cookie 中的特定字段)将用户请求路由到同一台后端服务器。分布式会话管理技术则是通过在多个服务器实例之间同步会话信息来保证一致性,如使用消息队列或专门的分布式会话管理框架,当一个服务器实例上的会话信息发生改变时,及时通知其他相关服务器实例更新会话信息。
- 跨域问题处理:
- CORS(跨域资源共享):在服务器端设置
Access-Control-Allow-Origin
等 CORS 头信息,指定允许访问的源。例如,如果单点登录系统的认证服务器域名为auth.example.com
,而系统 A 的域名为app1.example.com
,系统 B 的域名为app2.example.com
,在认证服务器的响应头中设置Access-Control-Allow-Origin: app1.example.com,app2.example.com
,允许系统 A 和系统 B 访问认证服务器的资源。同时,还可以设置其他相关头信息,如Access-Control-Allow-Methods
来指定允许的 HTTP 方法(如 GET、POST 等),Access-Control-Allow-Headers
来指定允许的请求头信息。 - JSONP(JSON with Padding):对于一些不支持 CORS 的老旧浏览器,可以采用 JSONP 方式。在客户端,通过动态创建
<script>
标签,将请求的 URL 设置为认证服务器的某个接口,并带上回调函数名作为参数。认证服务器在返回数据时,将数据包装在回调函数中进行响应。例如,客户端代码如下:
- CORS(跨域资源共享):在服务器端设置
function handleResponse(data) {// 处理认证服务器返回的数据
}var script = document.createElement('script');
script.src = 'https://auth.example.com/login?callback=handleResponse';
document.body.appendChild(script);
认证服务器在接收到请求后,根据用户的登录情况生成数据,并返回类似如下的响应:
handleResponse({ "status": "success", "user": { "id": 123, "name": "John" } });
- Token 认证:在跨域情况下,使用 Token 认证可以避免一些跨域问题。如前面所述,用户在登录后获取 Token,在访问其他系统时,将 Token 放在请求头(如
Authorization
头)中进行传递。系统在接收到请求后,通过验证 Token 的有效性来确认用户身份,而不需要依赖 Cookie 等可能存在跨域问题的机制。例如,在系统 A 登录后,用户获得一个 JWT Token,在访问系统 B 时,在请求头中添加Authorization: Bearer <token>
,系统 B 接收到请求后,解析 Token 并验证其有效性,如果有效则允许用户访问。
相关文章:
【面试】Java 记录一次面试过程 三年工作经验
2025 个人工作经验与基础概念 工作挑战及解决方式:这需要根据个人实际工作经历来回答,例如在项目中遇到性能瓶颈,通过代码优化、数据库索引调整或引入缓存机制等方式解决。单例模式: 常见的实现方式有饿汉式、懒汉式(…...
1/20赛后总结
1/20赛后总结 T1『讨论区管理员』的旅行 - BBC编程训练营 算法:IDA* 分数:0 damn it! Ac_code走丢了~~(主要是没有写出来)~~ T2华强买瓜 - BBC编程训练营 算法:双向DFS或者DFS剪枝 分数:0 Ac_code…...
Linux 高级路由与流量控制-用 tc qdisc 管理 Linux 网络带宽
大家读完记得觉得有帮助记得关注和点赞!!! 此分享内容比较专业,很多与硬件和通讯规则及队列,比较底层需要有技术功底人员深入解读。 Linux 的带宽管理能力 足以媲美许多高端、专用的带宽管理系统。 1 队列࿰…...
大模型GUI系列论文阅读 DAY1:《基于大型语言模型的图形用户界面智能体:综述》(6.6W 字长文)
摘要 图形用户界面(Graphical User Interfaces, GUIs)长期以来一直是人机交互的核心,为用户提供了直观且以视觉为驱动的方式来访问和操作数字系统。传统上,GUI交互的自动化依赖于基于脚本或规则的方法,这些方法在固定…...
【组件库】使用Vue2+AntV X6+ElementUI 实现拖拽配置自定义vue节点
先来看看实现效果: 【组件库】使用 AntV X6 ElementUI 实现拖拽配置自定义 Vue 节点 在现代前端开发中,流程图和可视化编辑器的需求日益增加。AntV X6 是一个强大的图形化框架,支持丰富的图形操作和自定义功能。结合 ElementUI,…...
考研408笔记之数据结构(三)——串
数据结构(三)——串 1. 串的定义和基本操作 本节内容很少,重点是串的模式匹配,所以对于串的定义和基本操作,我就简单提一些易错点。另外,串也是一种特殊的线性表,只不过线性表是可以存储任何东…...
Java 和 JavaScript 的区别
尽管名字相似,JavaScript 的名字中带有 “Java”,确实让很多人误以为它与 Java 有紧密联系。但实际上,它们是完全不同的语言,只是在 JavaScript 的发展历史中与 Java 有一定的关联。 1. JavaScript 的诞生背景 时间点࿱…...
Web3与传统互联网的对比:去中心化的未来路径
随着互联网技术的不断发展,Web3作为去中心化的新兴架构,正在逐步改变我们的网络体验。从传统的Web2到Web3,互联网的演进不仅是技术的革新,更是理念的变革。那么,Web3与传统互联网相比,到底有何不同…...
C++之初识模版
目录 1.关于模版的介绍 2.函数模版 2.1函数模板概念 2.2函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5模板参数的匹配原则 3.类模版 3.1类模板的定义格式 3.2 类模板的实例化 1.关于模版的介绍 C中的模板是一种通用编程工具,它允许程序员编…...
如何给自己的域名配置免费的HTTPS How to configure free HTTPS for your domain name
今天有小伙伴给我发私信,你的 https 到期啦 并且随手丢给我一个截图。 还真到期了。 javapub.net.cn 这个网站作为一个用爱发电的编程学习网站,用来存编程知识和面试题等,平时我都用业余时间来维护,并且还自费买了服务器和阿里云…...
什么是Memecoin?它如何在加密货币世界崭露头角
在加密货币的世界里,Memecoin已经成为一个越来越受欢迎的词汇。作为一种新兴的加密货币,Memecoin凭借其独特的性质和文化背景吸引了大量投资者和加密爱好者。本文将详细探讨Memecoin是什么、它的起源、以及为什么它在市场中越来越受到关注。 什么是Meme…...
[unity 高阶]使用ASE制作一个cubed的skybox的shader,跟做版本
第一步,导入ASE 此步骤不在此讲解,有时间再补充 第二步,创建shader 需要选择shader的类型,此处选择legacy/Unlit第三步,创建变量 根据默认shader中的变量 _Tint (“Tint Color”, Color) = (.5, .5, .5, .5)[Gamma] _Exposure (“Exposure”, Range(0, 8)) = 1.0_Rotat…...
Java复习第四天
一、代码题 1.相同的树 (1)题目 给你两棵二叉树的根节点p和q,编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 示例 1: 输入:p[1,2,3],q[1,2,3] 输出:true示例 2: 输…...
mysql数据库启动出现Plugin ‘FEEDBACK‘ is disabled.问题解决记录
本人出现该问题的环境是xampp,异常关机,再次在xampp控制面板启动mysql出现该问题。出现问题折腾数据库之前,先备份数据,将mysql目录下的data拷贝到其他地方,这很重要。 然后开始折腾。 查资料,会发现很多…...
Spring 是如何解决循环依赖问题
Spring 框架通过 三级缓存 机制来解决循环依赖问题。循环依赖是指两个或多个 Bean 相互依赖,形成一个闭环,例如 Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。Spring 通过提前暴露未完全初始化的 Bean 来解决这个问题。 以下是 Spring 解决…...
【论文笔记】TranSplat:深度refine的camera-required可泛化稀疏方法
深度信息在场景重建中是非常重要的先验,有一个精确的深度估计,重建质量起码提升一半,这一篇就是围绕着transformer优化深度来展开工作,进而提升GS的效果,感谢作者大佬们的work! 1 Abstract 与之前的3D重建方…...
营销2.0时代的挑战与开源AI智能名片2+1链动模式S2B2C商城小程序源码的解决方案
摘要:本文旨在探讨营销2.0时代企业在客户管理方面的挑战,并提出开源AI智能名片21链动模式S2B2C商城小程序源码作为解决方案。营销2.0虽然强调客户导向,但在实际操作中,企业往往无差别地对待所有客户,导致客户忠诚度下降…...
PHP教育系统小程序
🌐 教育系统:全方位学习新体验,引领未来教育风尚 🚀 教育系统:创新平台,智慧启航 📱 教育系统,一款深度融合科技与教育的创新平台,匠心独运地采用先进的ThinkPHP框架与U…...
开篇:吴恩达《机器学习》课程及免费旁听方法
课程地址: Machine Learning | Coursera 共包含三个子课程 Supervised Machine Learning: Regression and Classification | Coursera Advanced Learning Algorithms | Coursera Unsupervised Learning, Recommenders, Reinforcement Learning | Coursera 免费…...
zookeeper的介绍和简单使用
1 zookerper介绍 zookeeper是一个开源的分布式协调服务,由Apache软件基金会提供,主要用于解决分布式应用中的数据管理、状态同步和集群协调等问题。通过提供一个高性能、高可用的协调服务,帮助构建可靠的分布式系统。 Zookeeper的特点和功能…...
python学opencv|读取图像(三十八 )阈值自适应处理
【1】引言 前序学习了5种阈值处理方法,包括(反)阈值处理、(反)零值处理和截断处理,相关文章链接为: python学opencv|读取图像(三十三)阈值处理-灰度图像-CSDN博客 python学opencv|读取图像(三十四&#…...
C++ 条件变量-生产消费者模型
条件变量是一种线程同步机制,当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒. C11的条件变量提供了两个类: condition_variable:只支持与普通mutex搭配,效率更高。 condition_…...
Vue - ref( ) 和 reactive( ) 响应式数据的使用
一、ref( ) 在 Vue 3 中,ref() 是一个用于创建响应式引用的函数。它是 Vue 3 Composition API(组合式API) 的一部分,允许在组件中创建响应式数据。 使用对象:基本数据类型(String 、Number 、Boolean 、Null 等)、对…...
C语言初阶牛客网刷题——HJ73 计算日期到天数转换【难度:简单】
1. 题目描述——HJ73 计算日期到天数转换 牛客网OJ题链接 描述 每一年中都有 12 个月份。其中,1,3,5,7,8,10,12 月每个月有 31 天; 4,6,9,11 月每个月有 30 天;而对于 2 月,闰年时有29 天,平年时有 28 天。 现在&am…...
学到一些小知识关于Maven 与 logback 与 jpa 日志
1.jpa想要输出参数 logging:level:org.hibernate.orm.jdbc.bind: trace #打印SQL参数web: debug #web框架的日志级别就可以了, 2.Slf4j 其实 Slf4j 是一个日志接口规范,没有具体的实现 而 logback 是 Slf4j的一个实现 ,也是springboot3 的…...
Springboot3 自动装配流程与核心文件:imports文件
注:本文以spring-boot v3.4.1源码为基础,梳理spring-boot应用启动流程、分析自动装配的原理 如果对spring-boot2自动装配有兴趣,可以看看我另一篇文章: Springboot2 自动装配之spring-autoconfigure-metadata.properties和spring…...
1905电影网中国地区电影数据分析(一) - 数据采集、清洗与存储
文章目录 前言一、数据采集步骤及python库使用版本1. python库使用版本2. 数据采集步骤 二、数据采集网页分析1. 分析采集的字段和URL1.1 分析要爬取的数据字段1.2 分析每部电影的URL1.2 分析每页的URL 2. 字段元素标签定位 三、数据采集代码实现1. 爬取1905电影网分类信息2. 爬…...
postgresql15的启动
PostgreSQL是一个功能非常强大的、源代码开放的客户/服务器关系型数据库管理系统,且因为许可证的灵活,任何人都可以以任何目的免费使用、修改和分发PostgreSQL。现在国产数据库大力发展阶段,学习和熟悉postgresql的功能是非常有必要的&#x…...
基于SpringBoot的高校教师科研的设计与实现(源码+SQL脚本+LW+部署讲解等)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
位运算的基本概念+通过 Brian Kernighan算法计算 lowbit 实现的奇技淫巧 python
目录 引入判断奇偶位运算概念 进入正题Brian Kernighan 算法lowbit 介绍判断幂举一反三牛刀小试汉明重量总结 引入 判断奇偶 假设你不知道位运算为何物:你怎么判断奇偶呢? n int(input()) if n % 2 0:print(f"{n}是偶数") else:print(f&q…...
vscode环境中用仓颉语言开发时调出覆盖率的方法
在vscode中仓颉语言想得到在idea中利用junit和jacoco的覆盖率,需要如下几个步骤: 1.在vscode中搭建仓颉语言开发环境; 2.在源代码中右键运行[cangjie]coverage. 思路1:编写了测试代码的情况(包管理工具) …...
【测试】UI自动化测试
长期更新,建议关注收藏点赞! 目录 概论WEB环境搭建Selenium APPAppium 概论 使用工具和代码执行用例。 什么样的项目需要自动化? 需要回归测试、自动化的功能模块需求变更不频繁、项目周期长(功能测试时长:UI自动化测…...
ThinkPHP 8的多对多关联
【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…...
利用 SoybeanAdmin 实现前后端分离的企业级管理系统
引言 随着前后端分离架构的普及,越来越多的企业级应用开始采用这种方式来开发。前后端分离不仅提升了开发效率,还让前端和后端开发可以并行进行,减少了相互之间的耦合度。SoybeanAdmin 是一款基于 Spring Boot 和 MyBatis-Plus 的后台管理系…...
【Uniapp-Vue3】request各种不同类型的参数详解
一、参数携带 我们调用该接口的时候需要传入type参数。 第一种 路径名称?参数名1参数值1&参数名2参数值2 第二种 uni.request({ url:"请求路径", data:{ 参数名:参数值 } }) 二、请求方式 常用的有get,post和put 三种,默认是get请求。…...
【安当产品应用案例100集】034-安当KSP支持密评中存储数据的机密性和完整性
安当KSP是一套获得国密证书的专业的密钥管理系统。KSP的系统功能扩展图示如下: 我们知道商用密码应用安全性评估中,需要确保存储的数据不被篡改、删除或者破坏,必须采用合适的安全方案来确保存储数据的机密性和完整性。KSP能否满足这个需求呢…...
如何实现网页不用刷新也能更新
要实现用户在网页上不用刷新也能到下一题,可以使用 前端和后端交互的技术,比如 AJAX(Asynchronous JavaScript and XML)、Fetch API 或 WebSocket 来实现局部页面更新。以下是一个实现思路: 1. 使用前端 AJAX 或 Fetch…...
【真机调试】前端开发:移动端特殊手机型号有问题,如何在电脑上进行调试?
目录 前言一、怎么设置成开发者模式?二、真机调试基本步骤? 🚀写在最后 前言 edge浏览器 edge://inspect/#devices 谷歌浏览器(开tizi) chrome://inspect 一、怎么设置成开发者模式? Android 设备 打开设…...
ASP.NET Core 6.0 如何处理丢失的 Startup.cs 文件
介绍 .NET 6.0 已经发布,ASP.NET Core 6.0 也已发布。其中有不少变化让很多人感到困惑。例如,“谁动了我的奶酪”,它在哪里Startup.cs?在这篇文章中,我将深入研究这个问题,看看它移动到了哪里以及其他变化。…...
利用Java爬虫获取eBay商品详情:代码示例与教程
在当今的电商时代,获取商品详情数据对于市场分析、价格监控和竞品研究至关重要。eBay作为全球最大的电商平台之一,拥有海量的商品信息。通过Java爬虫技术,我们可以高效地获取这些数据,为商业决策提供支持。本文将详细介绍如何使用…...
graylog~认识一下-日志管理平台
1、介绍 Graylog 是一个开源的日志管理和分析平台,旨在帮助企业集中收集、存储、搜索和分析来自各种来源的日志数据。它提供了强大的实时日志处理能力,适用于大规模分布式系统和复杂的生产环境。 主要功能 集中化日志管理: 收集来自不同来源…...
Vue 拦截监听原理
Vue 渐进式JavaScript 框架 学习笔记 - Vue 拦截监听原理 目录 拦截监听原理 如何跟踪变化 拦截监听示例 观察者 注意:vue3的变化 总结 拦截监听原理 如何跟踪变化 当你把一个普通的Javascript 对象传入 Vue 实例作为data选项,Vue 将遍历此对象所有的proper…...
C# 解析 HTML 实战指南
在网页开发和数据处理的场景中,经常需要从 HTML 文档里提取有用的信息。C# 作为一门强大的编程语言,提供了丰富的工具和库来实现 HTML 的解析。这篇博客就带你深入了解如何使用 C# 高效地解析 HTML。 一、为什么要在 C# 中解析 HTML 在实际项目中&…...
idea新增java快捷键代码片段
最近在写一些算法题,有很多的List<List这种编写,想着能否自定义一下快捷键 直接在写代码输入:lli,即可看见提示...
网络与信息安全:企业如何正确实施电子邮件监控,防止内忧外患?
什么是电子邮件监控? 电子邮件监控对于保护公司免受因员工的恶意活动或外部攻击(如网络钓鱼、垃圾邮件等)而导致的不良事件的影响非常重要。实施员工电子邮件监控措施可能包括以下原因: 密切关注员工的官方电子邮件可确保员工有…...
blender 安装笔记 linux 2025
目录 linux安装blender: 运行后台渲染: 安装库: linux安装blender: # 进入下载目录 cd /shared_disk/users/lbg/soft/ # 下载 Blender 4.3.2 安装包 wget https://download.blender.org/release/Blender4.3/blender-4.3.2-l…...
99.11 金融难点通俗解释:净资产收益率(ROE)VS投资资本回报率(ROIC)VS总资产收益率(ROA)
目录 0. 承前1. 简述:三大收益率指标对比2. 比喻:三大指标对比2.1 简单对比2.2 生动比喻2.3 区别要点 3. 实际应用3.1 选择建议 4. 总结5. 实现代码 0. 承前 如果想更加全面清晰地了解金融资产组合模型进化论的体系架构,可参考: …...
【深度学习入门】深度学习知识点总结
一、卷积 (1)什么是卷积 定义:特征图的局部与卷积核做内积的操作。 作用:① 广泛应用于图像处理领域。卷积操作可以提取图片中的特征,低层的卷积层提取局部特征,如:边缘、线条、角。 ② 高层…...
最小距离和与带权最小距离和
1. 等权中位数 背景: 给定一系列整数,求一个整数x使得x在数轴上与所有整数在数轴上的距离和最小。 结论: 这一系列的整数按顺序排好后的中位数(偶数个整数的中位数取 n 2 或 n 2 1 \frac{n}{2}或\frac{n}{2}1 2n或2n1都可)一定是所求点…...
有哪些常见的 Vue 错误?
在使用 Vue.js 开发应用时,开发者可能会遇到各种错误。以下是一些常见的 Vue 错误以及如何避免它们:为了更详细地解释常见的 Vue.js 错误,我们可以深入探讨每个类别,并提供更多的背景信息和解决方案。以下是针对常见错误的扩展说明…...