站长信息
jeffery.xu
jeffery.xu

软件工程师

欢迎访问我的个人笔记网站!我是一名热爱技术的开发者,专注于Web开发和技术分享。

811495111@qq.com
18521510875
筛选

个人笔记

Vue的生命周期钩子方法
java学习

生命周期钩子方法是 Vue 组件在创建、挂载、更新和销毁等不同阶段自动调用的特殊函数。你可以在这些钩子里编写代码,实现如数据初始化、资源清理等操作。

常见生命周期钩子及其执行时机:


onMounted:组件挂载到页面后执行(适合请求数据、DOM操作)。
onBeforeMount:组件挂载前执行。
onUpdated:组件更新后执行。
onBeforeUpdate:组件更新前执行。
onUnmounted:组件卸载前执行(适合清理定时器、事件监听等)。
这些钩子只能在 setup() 或 <script setup> 语法中使用。

redis需求
java学习

Redis 的软件需求主要包括操作系统、内存、硬盘、网络等基础配置,以及编译环境要求:

操作系统

Redis官方推荐在 Linux 环境下运行,因其基于内存存储的特性,Linux能更好发挥其性能。 ‌

内存要求

Redis默认将数据存储在内存中,存储大量数据时需确保服务器具备足够内存(建议至少1GB)。若启用持久化功能(如 RDB  AOF ),还需预留硬盘空间用于数据备份。 ‌

硬盘要求

虽然数据主要存储在内存,但需配置硬盘用于持久化存储(如RDB/AOF文件)。建议分配至少10GB硬盘空间,确保数据备份和恢复。 ‌

网络要求

Redis作为网络服务,需确保网络稳定性和可靠性,客户端通过TCP/IP协议与服务器通信。 ‌

编译环境

安装时需配置C编译器(如 GCC )和 make工具 ,用于编译源码(适用于自定义安装或调试)。 ‌

final关键字
java学习

在Java中,`final` 关键字用于修饰变量、方法或类,表示"最终的"、"不可改变的"。

对于实例变量,`final` 的含义是:

1. **不可重新赋值**:一旦被初始化后,就不能再改变引用
2. **必须初始化**:必须在声明时或构造函数中进行初始化

在你的代码中:

```java
private final RolePermissionMapper rolePermissionMapper;
```

这意味着:
- `rolePermissionMapper` 变量一旦被赋值后就不能再指向其他对象
- 必须通过构造函数注入来初始化(配合 `@RequiredArgsConstructor` 注解)

**为什么在Spring中使用 `final`?**

1. **不可变性**:确保依赖注入后不会被意外修改
2. **线程安全**:final 字段在多线程环境下是安全的
3. **构造函数注入**:配合 Lombok 的 `@RequiredArgsConstructor` 自动生成构造函数
4. **最佳实践**:Spring 官方推荐使用构造函数注入而非字段注入

等效的传统写法:

```java
@Service
public class RolePermissionServiceImpl implements IRolePermissionService {
    
    private final RolePermissionMapper rolePermissionMapper;
    
    public RolePermissionServiceImpl(RolePermissionMapper rolePermissionMapper) {
        this.rolePermissionMapper = rolePermissionMapper;
    }
}
```

使用 `final` + `@RequiredArgsConstructor` 让代码更简洁且更安全。

调整初始化数据
java学习
package com.mobizone.kb.config;

import com.mobizone.kb.entity.*;
import com.mobizone.kb.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class DataInitializerConfig implements CommandLineRunner {

@Autowired
private IUsersService usersService;

@Autowired
private IRolesService rolesService;

@Autowired
private IUserRolesService userRolesService;

@Autowired
private IMenuService menuService;

@Autowired
private IPermissionService permissionService;

@Autowired
private IRolePermissionService rolePermissionService;

@Autowired
private IRoleMenuService roleMenuService;

@Override
public void run(String... args) throws Exception {
// 初始化权限
initPermissions();

// 初始化菜单
initMenus();

// 初始化角色
initRoles();

// 初始化管理员账号
Long adminUserId = initAdminUser();

// 如果成功创建了管理员账号,则分配角色
if (adminUserId != null) {
assignRolesToAdmin(adminUserId);
}

// 为超级管理员分配所有权限
assignPermissionsToSuperAdmin();
}

private void initPermissions() {
// 用户管理权限
createPermissionIfNotExists("user:list", "用户列表", "user", "read", "查看用户列表");
createPermissionIfNotExists("user:add", "添加用户", "user", "create", "创建新用户");
createPermissionIfNotExists("user:edit", "编辑用户", "user", "update", "编辑用户信息");
createPermissionIfNotExists("user:delete", "删除用户", "user", "delete", "删除用户");

// 菜单管理权限
createPermissionIfNotExists("menu:list", "菜单列表", "menu", "read", "查看菜单列表");
createPermissionIfNotExists("menu:add", "添加菜单", "menu", "create", "创建新菜单");
createPermissionIfNotExists("menu:edit", "编辑菜单", "menu", "update", "编辑菜单信息");
createPermissionIfNotExists("menu:delete", "删除菜单", "menu", "delete", "删除菜单");

// 角色管理权限
createPermissionIfNotExists("role:list", "角色列表", "role", "read", "查看角色列表");
createPermissionIfNotExists("role:add", "添加角色", "role", "create", "创建新角色");
createPermissionIfNotExists("role:edit", "编辑角色", "role", "update", "编辑角色信息");
createPermissionIfNotExists("role:delete", "删除角色", "role", "delete", "删除角色");

// 权限管理权限
createPermissionIfNotExists("permission:list", "权限列表", "permission", "read", "查看权限列表");
createPermissionIfNotExists("permission:add", "添加权限", "permission", "create", "创建新权限");
createPermissionIfNotExists("permission:edit", "编辑权限", "permission", "update", "编辑权限信息");
createPermissionIfNotExists("permission:delete", "删除权限", "permission", "delete", "删除权限");
}

private void createPermissionIfNotExists(String code, String name, String resource, String action, String description) {
Permission existingPermission = permissionService.getPermissionByCode(code);
if (existingPermission == null) {
Permission permission = new Permission();
permission.setPermissionCode(code);
permission.setPermissionName(name);
permission.setResource(resource);
permission.setAction(action);
permission.setDescription(description);
permission.setCreatedAt(LocalDateTime.now());
permissionService.addPermission(permission);
}
}

private void initMenus() {
// 系统管理主菜单
Long systemMenuId = createMenuIfNotExists("系统管理", "system", "/system", null, "system", 0L, 1, 100);

// 用户管理菜单
createMenuIfNotExists("用户管理", "user", "/system/user", "system/user/index", "user", systemMenuId, 1, 101);

// 角色管理菜单
createMenuIfNotExists("角色管理", "role", "/system/role", "system/role/index", "role", systemMenuId, 1, 102);

// 菜单管理菜单
createMenuIfNotExists("菜单管理", "menu", "/system/menu", "system/menu/index", "menu", systemMenuId, 1, 103);

// 权限管理菜单
createMenuIfNotExists("权限管理", "permission", "/system/permission", "system/permission/index", "permission", systemMenuId, 1, 104);
}

private Long createMenuIfNotExists(String name, String code, String path, String component,
String icon, Long parentId, Integer type, Integer sort) {
// 这里需要你实现一个根据code查询菜单的方法
List<Menu> allMenus = menuService.getAllMenus();
Menu existingMenu = allMenus.stream()
.filter(menu -> code.equals(menu.getMenuCode()))
.findFirst()
.orElse(null);

if (existingMenu == null) {
Menu menu = new Menu();
menu.setMenuName(name);
menu.setMenuCode(code);
menu.setPath(path);
menu.setComponent(component);
menu.setIcon(icon);
menu.setParentId(parentId);
menu.setMenuType(type);
menu.setSortOrder(sort);
menu.setStatus(1);
menu.setCreatedAt(LocalDateTime.now());
menu.setUpdatedAt(LocalDateTime.now());
menuService.addMenu(menu);
return menu.getId();
}
return existingMenu.getId();
}

private void initRoles() {
if (!rolesService.roleExists("SUPER_ADMIN")) {
Roles adminRole = new Roles();
adminRole.setRole_name("超级管理员");
adminRole.setRole_code("SUPER_ADMIN");
adminRole.setDescription("超级管理员,拥有所有权限");
rolesService.addRole(adminRole);
System.out.println("超级管理员角色创建成功");
}

if (!rolesService.roleExists("ROLE_ADMIN")) {
Roles adminRole = new Roles();
adminRole.setRole_name("管理员");
adminRole.setRole_code("ROLE_ADMIN");
adminRole.setDescription("普通管理员,拥有特定权限");
rolesService.addRole(adminRole);
System.out.println("管理员角色创建成功");
}

if (!rolesService.roleExists("ROLE_USER")) {
Roles userRole = new Roles();
userRole.setRole_name("普通用户");
userRole.setRole_code("ROLE_USER");
userRole.setDescription("普通用户,拥有基本权限");
rolesService.addRole(userRole);
System.out.println("普通用户角色创建成功");
}
}

private Long initAdminUser() {
if (!usersService.userExists("admin")) {
Users admin = new Users();
admin.setUsername("admin");
admin.setPassword("Password01!");
admin.setNickname("系统管理员");
admin.setCreated_by(1L);
admin.setPhone("18521510875");
admin.setEmail("811495111@qq.com");
usersService.addUser(admin);
System.out.println("管理员账号创建成功");
return admin.getId();
}
return usersService.getUserByUserName("admin").getId();
}

private void assignRolesToAdmin(Long adminUserId) {
Roles adminRole = rolesService.getByRoleCode("SUPER_ADMIN");
if (!userRolesService.hasRole(adminUserId, adminRole.getId())) {
UserRoles userRole = new UserRoles();
userRole.setUserId(adminUserId);
userRole.setRoleId(adminRole.getId());
userRolesService.addUserRole(userRole);
System.out.println("管理员角色分配成功");
}
}

private void assignPermissionsToSuperAdmin() {
Roles superAdminRole = rolesService.getByRoleCode("SUPER_ADMIN");
if (superAdminRole != null) {
// 获取所有权限ID
List<Long> allPermissionIds = permissionService.getAllPermissions().stream()
.map(Permission::getId)
.collect(Collectors.toList());

// 检查是否已经分配了权限
List<Long> existingPermissions = rolePermissionService.getPermissionIdsByRoleId(superAdminRole.getId());
if (existingPermissions.isEmpty() && !allPermissionIds.isEmpty()) {
rolePermissionService.assignPermissionsToRole(superAdminRole.getId(), allPermissionIds);
System.out.println("超级管理员权限分配成功");
}

// 分配菜单权限
assignMenusToSuperAdmin(superAdminRole.getId());
}
}
private void assignMenusToSuperAdmin(Long roleId) {
// 获取所有菜单ID
List<Long> allMenuIds = menuService.getAllMenus().stream()
.map(Menu::getId)
.collect(Collectors.toList());

if (!allMenuIds.isEmpty()) {
// 检查是否已经分配了菜单权限
List<Long> existingMenuIds = roleMenuService.getMenuIdsByRoleId(roleId);

if (existingMenuIds.isEmpty()) {
// 为超级管理员分配所有菜单权限
roleMenuService.assignMenusToRole(roleId, allMenuIds);
System.out.println("超级管理员菜单权限分配成功,共分配 " + allMenuIds.size() + " 个菜单");
} else {
System.out.println("超级管理员已拥有菜单权限,跳过分配");
}
}
}
}
拦截器和过滤器的区别
java学习

过滤器(Filter)和拦截器(Interceptor)都可以用来处理请求,但它们有以下主要区别:

## 1. 执行时机和顺序

```
请求 → Filter → DispatcherServlet → Interceptor → Controller → Interceptor → Filter → 响应
```

- **Filter**:在Servlet容器层面执行,比Spring框架更早介入
- **Interceptor**:在Spring MVC框架层面执行,在DispatcherServlet之后

## 2. 技术层面

| 特性 | Filter | Interceptor |
|------|--------|-------------|
| 技术标准 | Java EE标准(Servlet规范) | Spring框架特有 |
| 依赖容器 | Servlet容器 | Spring容器 |
| 依赖注入 | 不支持@Autowired | 支持@Autowired |
| 作用范围 | 所有请求(包括静态资源) | 仅Spring MVC处理的请求 |

## 3. 功能差异

### Filter特点:
- 可以修改请求和响应对象
- 能够处理所有类型的请求(包括静态资源)
- 适合做字符编码、请求日志、权限验证等

### Interceptor特点:
- 可以访问Spring上下文
- 能获取到Handler(Controller方法信息)
- 提供更细粒度的控制点
- 适合做权限验证、参数验证、性能监控等

## 4. 针对你的日志需求建议

对于OperationLog功能,**推荐使用拦截器**,原因:

1. **可以获取用户信息**:拦截器可以注入Spring组件,更容易获取当前用户
2. **性能更好**:只处理API请求,不处理静态资源
3. **功能更强**:可以获取Controller方法信息,记录更详细的操作类型

## 5. 优化后的拦截器实现

基于你已有的接口,这里是改进版本:

```java
package com.mobizone.kb.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mobizone.kb.entity.OperationLog;
import com.mobizone.kb.service.IOperationLogService;
import com.mobizone.kb.utils.IpUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;

@Component
@RequiredArgsConstructor
@Slf4j
public class OperationLogInterceptor implements HandlerInterceptor {

    private final IOperationLogService operationLogService;
    private final ObjectMapper objectMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        request.setAttribute("startTime", System.currentTimeMillis());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                              Object handler, Exception ex) {
        try {
            Long startTime = (Long) request.getAttribute("startTime");
            int duration = startTime != null ? (int) (System.currentTimeMillis() - startTime) : 0;

            OperationLog operationLog = new OperationLog();
            operationLog.setUserId(getCurrentUserId(request));
            operationLog.setOperation(getOperationName(handler, request));
            operationLog.setMethod(request.getMethod());
            operationLog.setUrl(request.getRequestURI());
            operationLog.setIp(IpUtils.getClientIp(request));
            operationLog.setUserAgent(request.getHeader("User-Agent"));
            operationLog.setRequestData(getRequestParams(request));
            operationLog.setStatus(response.getStatus() >= 200 && response.getStatus() < 300 ? 1 : 0);
            
            if (ex != null) {
                operationLog.setErrorMsg(ex.getMessage());
                operationLog.setStatus(0);
            }
            
            operationLog.setDuration(duration);
            operationLog.setCreatedAt(LocalDateTime.now());

            operationLogService.saveLog(operationLog);

        } catch (Exception e) {
            log.error("记录操作日志失败", e);
        }
    }

    private String getOperationName(Object handler, HttpServletRequest request) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            String className = handlerMethod.getBeanType().getSimpleName();
            String methodName = handlerMethod.getMethod().getName();
            return className + "." + methodName;
        }
        return request.getMethod() + " " + request.getRequestURI();
    }

    private Long getCurrentUserId(HttpServletRequest request) {
        // 根据你的认证方式获取用户ID
        Object userId = request.getAttribute("userId");
        return userId != null ? (Long) userId : null;
    }

    private String getRequestParams(HttpServletRequest request) {
        try {
            return request.getQueryString();
        } catch (Exception e) {
            return null;
        }
    }
}
```

这样的实现更适合Spring Boot项目,既能记录详细的操作信息,又不会影响性能。

webstorm快捷键
编程技巧

  press r + enter to restart the server
  press u + enter to show server url
  press o + enter to open in browser
  press c + enter to clear console
  press q + enter to quit

Redis 设置密码
java学习
sudo nano /etc/redis/redis.conf

# requirepass foobared
requirepass your_password_here

sudo service redis-server restart

redis-cli -h localhost -p 6379 -a your_password_here ping

application.yml:
spring:
redis:
host: 127.0.0.1
port: 6379
password: your_password_here
timeout: 2000ms
修改设计
java学习

完整的表结构设计:

## 完整的表结构设计

```sql
-- 用户表(已有,稍作调整)
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100) UNIQUE,
    nickname VARCHAR(50),
    avatar VARCHAR(255),
    phone VARCHAR(20),
    status TINYINT DEFAULT 1 COMMENT '1:正常 0:禁用',
    created_by BIGINT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_username (username),
    INDEX idx_status (status)
);

-- 角色表(已有,保持不变)
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) UNIQUE NOT NULL,
    role_code VARCHAR(50) UNIQUE NOT NULL,
    description TEXT,
    status TINYINT DEFAULT 1 COMMENT '1:正常 0:禁用',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 用户角色关联表(已有,保持不变)
CREATE TABLE user_roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    UNIQUE KEY uk_user_role (user_id, role_id)
);

-- 菜单表(新增 - 核心)
CREATE TABLE menus (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    parent_id BIGINT DEFAULT 0 COMMENT '父菜单ID,0为顶级菜单',
    menu_name VARCHAR(50) NOT NULL,
    menu_code VARCHAR(50) UNIQUE NOT NULL COMMENT '菜单编码',
    path VARCHAR(200) COMMENT '路由地址',
    component VARCHAR(255) COMMENT '组件路径',
    redirect VARCHAR(200) COMMENT '重定向地址',
    menu_type TINYINT NOT NULL COMMENT '菜单类型:1目录 2菜单 3按钮',
    visible TINYINT DEFAULT 1 COMMENT '是否显示:1显示 0隐藏',
    status TINYINT DEFAULT 1 COMMENT '菜单状态:1正常 0停用',
    icon VARCHAR(100) COMMENT '菜单图标',
    sort_order INT DEFAULT 0 COMMENT '显示顺序',
    meta JSON COMMENT '路由元信息(标题、缓存等)',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_parent_id (parent_id),
    INDEX idx_status (status)
);

-- 角色菜单关联表(新增 - 核心)
CREATE TABLE role_menus (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    menu_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (menu_id) REFERENCES menus(id) ON DELETE CASCADE,
    UNIQUE KEY uk_role_menu (role_id, menu_id)
);

-- 权限表(可选 - 更细粒度控制)
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    permission_name VARCHAR(50) NOT NULL,
    permission_code VARCHAR(50) UNIQUE NOT NULL,
    description TEXT,
    resource VARCHAR(100) COMMENT '资源标识',
    action VARCHAR(50) COMMENT '操作类型',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 角色权限关联表(可选)
CREATE TABLE role_permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
    UNIQUE KEY uk_role_permission (role_id, permission_id)
);

-- 操作日志表(推荐)
CREATE TABLE operation_logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT,
    operation VARCHAR(50) NOT NULL COMMENT '操作类型',
    method VARCHAR(10) COMMENT 'HTTP方法',
    url VARCHAR(500) COMMENT '请求URL',
    ip VARCHAR(50) COMMENT 'IP地址',
    user_agent TEXT COMMENT '用户代理',
    request_data JSON COMMENT '请求参数',
    response_data JSON COMMENT '响应数据',
    status TINYINT COMMENT '操作状态:1成功 0失败',
    error_msg TEXT COMMENT '错误信息',
    duration INT COMMENT '耗时(毫秒)',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
    INDEX idx_user_id (user_id),
    INDEX idx_created_at (created_at)
);
```

## 初始化数据

```sql
-- 插入默认角色
INSERT INTO roles (role_name, role_code, description) VALUES
('超级管理员', 'SUPER_ADMIN', '系统超级管理员'),
('管理员', 'ADMIN', '普通管理员'),
('用户', 'USER', '普通用户');

-- 插入菜单数据
INSERT INTO menus (parent_id, menu_name, menu_code, path, component, menu_type, icon, sort_order, meta) VALUES
-- 系统管理
(0, '系统管理', 'SYSTEM', '/system', 'Layout', 1, 'system', 1, '{"title": "系统管理"}'),
(1, '用户管理', 'USER_MANAGE', '/system/user', 'system/user/index', 2, 'user', 1, '{"title": "用户管理"}'),
(1, '角色管理', 'ROLE_MANAGE', '/system/role', 'system/role/index', 2, 'role', 2, '{"title": "角色管理"}'),
(1, '菜单管理', 'MENU_MANAGE', '/system/menu', 'system/menu/index', 2, 'menu', 3, '{"title": "菜单管理"}'),
-- 用户管理按钮权限
(2, '新增用户', 'USER_ADD', '', '', 3, '', 1, '{}'),
(2, '编辑用户', 'USER_EDIT', '', '', 3, '', 2, '{}'),
(2, '删除用户', 'USER_DELETE', '', '', 3, '', 3, '{}'),
-- 角色管理按钮权限  
(3, '新增角色', 'ROLE_ADD', '', '', 3, '', 1, '{}'),
(3, '编辑角色', 'ROLE_EDIT', '', '', 3, '', 2, '{}'),
(3, '删除角色', 'ROLE_DELETE', '', '', 3, '', 3, '{}'),
(3, '分配权限', 'ROLE_ASSIGN', '', '', 3, '', 4, '{}');

-- 给超级管理员分配所有菜单权限
INSERT INTO role_menus (role_id, menu_id) 
SELECT 1, id FROM menus;
```

## 主要改进点

1. **新增菜单表** - 支持多级菜单结构
2. **角色菜单关联** - 实现 RBAC 权限控制  
3. **菜单类型区分** - 目录/菜单/按钮三种类型
4. **路由元信息** - 使用 JSON 字段存储 Vue Router meta
5. **操作日志** - 记录用户操作行为
6. **索引优化** - 添加必要的数据库索引

这个结构可以满足你的所有需求:用户管理、角色管理、多级菜单配置、权限关联等。

ARM64架构说明
java学习

Windows ARM64是指适用于ARM64架构处理器的Windows操作系统版本。


ARM64架构说明
ARM64 - 64位ARM处理器架构
与x64区别 - x64是Intel/AMD处理器,ARM64是ARM处理器
性能特点 - ARM处理器通常更省电,适合移动设备
常见ARM64设备
Apple M系列芯片 - MacBook Air/Pro(M1/M2/M3)
Surface Pro X - 微软ARM版本平板
部分轻薄本 - 搭载高通骁龙处理器
软件兼容性
下载软件时需要注意:


WebStorm - 选择对应ARM64版本
Node.js - 下载Windows ARM64版本
其他工具 - 确认是否有ARM64原生支持
检查您的系统
在Windows中按 Win + R,输入 msinfo32,查看"系统类型":


显示"ARM64"表示是ARM架构
显示"x64"表示是Intel/AMD架构

配置swagger
java学习
package com.mobizone.kb.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.Components;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("Knowledge Base API")
                        .version("1.0.0")
                        .description("知识库管理系统API文档")
                        .contact(new Contact()
                                .name("Your Name")
                                .email("your-email@example.com")))
                .addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
                .components(new Components()
                        .addSecuritySchemes("bearerAuth",
                                new SecurityScheme()
                                        .type(SecurityScheme.Type.HTTP)
                                        .scheme("bearer")
                                        .bearerFormat("JWT")));
    }
}