站长信息
jeffery.xu
jeffery.xu

软件工程师

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

811495111@qq.com
18521510875
筛选

个人笔记

第四章-破败佛源镇
游戏攻略

丹青苑:回真武祭坛附近松竹幽径的洞口,和说书人对话,传送到第四章的丹青苑→走出房间,对话方瑶、何有哉、陶青、吴刚→丹青苑侧边小路下来,亭子里见到熊猫→丹青苑中间对话说书人,答应帮忙(如果拒绝,会在击败僰人巫祝后触发“僰人承继者”结局)→走出丹青苑,神龛旁对话安文思→佛源镇

蜀王祠:丹青苑→佛源镇→罗家大院打梦魇心魔→进着火的蜀王祠激活神龛

丹青侧院:蜀王祠二楼树桥出去→上山在崖边见到年大侠,他身旁是好兄弟谯麟的尸体→走隐形桥到丹青侧院→在神龛入梦刷新后,年大侠在丹青侧院的房子里独自哀伤→回到刚才年大侠坐的崖边,捡年大侠的武器、套装→下山去唐王行宫

唐王行宫:唐王行宫池塘见到僰人巫祝→开黄色先神之言门进入行宫菜园→安文思就躺在菜园神龛附近的坑里祈祷,这就是之前老船夫挖的那个坑→从行宫正门离开→行宫关隘见到僰人巫祝→打赤羽红岚→开红门来到秽柳滩,见到老船夫

僰人祀所(蜀王祠地下):秽柳滩→地宫入口→蜀王祠地下→打僰人巫祝

普元寺:蜀王祠→竹林小院→普元寺→打守护瑜夏的锦衣卫千户→普元寺神龛旁,之前见到垂髫小童的地方,对话方瑶→打碎附近的药罐,捡到垂髫小童、陶罐小童哥哥、老婆婆的遗物→回丹青苑,方瑶在门里躲着,但她不愿意开门→回普元寺,向瑜夏展示女主父亲的锦衣卫腰牌,或者红岚的同心锦囊→上三楼对话何有哉,给他看实验手记·残页三→燃灯洞窟

燃灯洞窟:对话玄阳子,保持沉默不要给他莹羽凿子(如果给他莹羽凿子,通关后会触发“饲药之人”结局,会被他做成药罐子)→深入洞窟,BOSS房门口对话素衣侠女→打真龙天子→僰人古都的古都入口→神龛旁对话玄阳子,旁边瑜夏和夫君躺在一起,入梦刷新后捡到明妃套装

衣冠冢:传送到真武山鸟人居所,过桥来到衣冠冢→打赵云

Transition vue3必须单一元素
java学习

Vue 3 的 <Transition> 组件要求被包裹的内容必须是单一根元素(一个 DOM 元素),否则会报 “Component inside <transition> renders non-element root node that cannot be animated.” 这个警告,并且内容区会不显示。</transition>
你在 Permission.vue 的 <template> 最外层加了一个 <div>,把所有内容包裹起来,这样就变成了单一根元素,警告消失,内容也能正常显示。
总结:
只要内容页 <template> 只有一个根元素,Vue 的 <Transition> 动画和内容渲染都能正常工作。
这是 Vue 官方推荐的写法,所有内容页都建议这样处理。

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

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

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


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

ping端口
编程技巧

 telnet 127.0.0.1 6379

telnet功能需要在控制面板,程序与功能中勾选出来,同IIS

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` 让代码更简洁且更安全。

第三章-真武山
游戏攻略

古战场关隘:从望山村神龛上山,村子入口处的阶梯旁,有个隐藏的洞口通往真武山→洞里对话白袍老者→古战场关隘房子后面捡到【染血蒸面兔】→穿过洞后,路边看到传教士安文思留下的忏悔,获得【贵重物品】药箱→篝火旁对话何有哉、方瑶→难民营地

难民营地:对话白袍老者,选择同意→桥头对话陶罐小童,给他染血蒸面兔,刷新后掉落破烂的纸张→帐篷处对话出山帮三小孩,答应沈静安救雪堡管家→神龛旁对话年大侠、安文思、吴刚、陶青。安慰年大侠选择“不是”。同意听安文思祈祷,然后药箱给安文思

鸟人居所:从白袍老者旁的梯子前往森林→鸟人居所神龛旁,对话红衣少女,选择认同→鸟人沼泽

鸟人沼泽:在鸟人沼泽神龛木屋的房顶附近,干掉怪物得到【贵重物品】胖子的木牌,同时捡到【贵重物品】麻杆的木牌→高处鸟人的窝里,捡到【贵重物品】密函→穿过洞后,过桥对话白袍老者→衣冠冢暂时没有BOSS→推倒两个树桥打通回鸟人居所神龛捷径→召唤白袍老者一起打僰人古巫→通往大西军总营的隧道里,对话利类思→回难民营地

难民营地:从利类思所在隧道岔路回到难民营地,(女道士心魔线)如果之前在蜀王祠给女道士道士令牌,她的另一个师弟无为子,会躲在帐篷里,害怕师姐杀他。刷新后回帐篷看到无为子尸体,女道士会攻击你→桥头对话年大侠、陶罐小童→安文思身旁,听到帐篷里的夫妻在聊码头坐船的事→把码头坐船的消息告诉陶罐小童→从鸟人居所神龛过桥到衣冠冢,对话白袍老者

真武观:经过大西军总营进入真武观→灵官殿神龛旁对话李定国,给他密函→探索真武观主殿,中间最高的是三清殿,(女道士清醒线)在三清殿下楼时对话在法阵打坐的女道士,等打张献忠的时候可以召唤她帮忙→打刘文秀→开门回难民营地,桥头门口对话年大侠

难民营地:真元丹给沈静安,刷新后再对话

真武观上层:经过真武观上层来到藏书阁→藏书阁楼上对话说书人→说书人旁的洞里,书架上拿到【贵重物品】真武观典籍→经过房顶,栈道上对话素衣侠女→真武祭坛

真武祭坛:祭坛大门神龛旁,对话利类思,旁边捡到【贵重物品】圣牌→回难民营地,圣牌给安文思→回普元寺,真武观典籍给玄阳子→在真武祭坛打张献忠(女道士清醒线)吹骨哨召唤女道士帮你打,打完后对话女道士,刷新后掉落套装→回普元寺,对话玄阳子

松竹幽径:穿过真武祭坛旁烧毁的房屋→松竹幽径洞口对话年大侠,先不和说书人对话,他会把你传送到第四章→洞外岔路尽头见到熊猫→穿过洞来到江望渡口

江望渡口:对话孙可望、李定国→对话雪堡管家、沈静安→胖子木牌、麻杆木牌都给沈静安→对话陶罐小童→刷新后再来,李定国掉落武器、陶罐小童掉落陶罐头罩→再对话雪堡管家和沈静安,刷新后掉落雪堡管家套装→在码头对话老船夫→乘船前往白家旧宅

白家旧宅:神龛旁对话何有哉→洞里白家祠堂前,对话素衣侠女,调查女主父亲的遗书得到【贵重物品】锦衣卫腰牌→打白克虏→对话何有哉,给他看实验手记·残页二→回江望渡口,对话老船夫→回普元寺,对话玄阳子

调整初始化数据
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