站长信息
jeffery.xu
jeffery.xu

软件工程师

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

811495111@qq.com
18521510875
筛选

个人笔记

java知识库网站初步设想
编程技巧

前后端分离 + Spring Boot + MyBatis + MySQL 暂定的项目架构:

## 后端技术栈

```xml
<!-- 主要依赖 -->
Spring Boot 3.x
├── Spring Security (认证授权)
├── MyBatis Plus (数据访问层)
├── MySQL 8.0 (数据库)
├── Redis (缓存)
├── JWT (Token认证)
└── Swagger (API文档)
```

## 项目结构

```
knowledge-base-backend/
├── src/main/java/com/example/kb/
│   ├── config/          # 配置类
│   ├── controller/      # 控制器
│   ├── service/         # 业务层
│   ├── mapper/          # MyBatis映射接口
│   ├── entity/          # 实体类
│   ├── dto/             # 数据传输对象
│   ├── vo/              # 视图对象
│   ├── common/          # 公共类
│   └── utils/           # 工具类
├── src/main/resources/
│   ├── mapper/          # MyBatis XML映射文件
│   ├── application.yml  # 配置文件
│   └── static/          # 静态资源
└── pom.xml
```

## Maven依赖配置

```xml
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    <!-- MyBatis Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.4</version>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    
    <!-- Swagger -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.2.0</version>
    </dependency>
</dependencies>
```

## 核心实体类设计

### User实体类
```java
@Data
@TableName("users")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    private String password;
    private String email;
    private String nickname;
    private String avatar;
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdAt;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedAt;
}
```

### Article实体类
```java
@Data
@TableName("articles")
public class Article {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String title;
    private String content;
    private String summary;
    private Long categoryId;
    private Long authorId;
    private Integer viewCount;
    private Integer likeCount;
    private Integer status;
    private Integer isTop;
    private String tags;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdAt;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedAt;
}
```

## MyBatis配置

### application.yml
```yaml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/knowledge_base?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: your_password
    
  redis:
    host: localhost
    port: 6379
    database: 0

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.kb.entity
```

## 前端技术栈

### 前台用户端
```json
{
  "技术栈": {
    "框架": "Vue 3 + Vite",
    "UI库": "Element Plus",
    "路由": "Vue Router 4", 
    "状态管理": "Pinia",
    "HTTP客户端": "Axios",
    "编辑器": "Vditor/TinyMCE"
  }
}
```

### 后台管理端
```json
{
  "技术栈": {
    "框架": "Vue 3 + Vite",
    "UI库": "Element Plus",
    "模板": "vue-element-plus-admin",
    "图表": "ECharts",
    "表格": "Element Plus Table"
  }
}
```

## API接口设计示例

### 文章相关接口
```java
@RestController
@RequestMapping("/api/articles")
public class ArticleController {
    
    @GetMapping("/list")
    public R<IPage<ArticleVO>> getArticleList(@RequestParam Map<String, Object> params) {
        // 分页查询文章列表
    }
    
    @GetMapping("/{id}")
    public R<ArticleVO> getArticleById(@PathVariable Long id) {
        // 获取文章详情
    }
    
    @PostMapping
    public R<Void> saveArticle(@RequestBody ArticleDTO articleDTO) {
        // 新增文章
    }
    
    @PutMapping("/{id}")
    public R<Void> updateArticle(@PathVariable Long id, @RequestBody ArticleDTO articleDTO) {
        // 更新文章
    }
}
```

## 开发建议

1. **数据库设计**:先创建数据库表结构
2. **后端开发**:从Entity → Mapper → Service → Controller的顺序开发
3. **前端开发**:先搭建基础框架,再开发具体页面
4. **接口联调**:使用Swagger进行接口测试

需要我提供具体的某个模块的详细代码吗?比如用户认证、文章管理等。

WSL上安装java
编程技巧

# 下载Oracle JDK 24
wget https://download.oracle.com/java/24/latest/jdk-24_linux-x64_bin.tar.gz

# 解压到/opt目录
sudo tar -xzf jdk-24_linux-x64_bin.tar.gz -C /opt/

nano ~/.bashrc

export JAVA_HOME=/opt/jdk-24.0.2/
export JAVA_BIN=$JAVA_HOME/bin
export JAVA_LIB=$JAVA_HOME/lib
export CLASSPATH=.:$JAVA_LIB/tools.jar:$JAVA_LIB/dt.jar
export PATH=$JAVA_BIN:$PATH

source ~/.bashrc

source:Shell内置命令,用于执行脚本文件中的命令
.bashrc:Bash配置文件,包含用户的Shell环境设置
作用
重新加载当前用户的Bash配置文件,使新的环境变量和设置立即生效,而无需重新登录或重启终端。

# 检查环境变量
echo $JAVA_HOME
echo $PATH

# 检查Java是否可用
java -version

sudo apt update && sudo apt upgrade -y 解释
编程技巧

sudo:以管理员权限执行
apt:Ubuntu/Debian的包管理器
update:更新软件包列表(不安装软件,只更新可用软件的信息)
&&:逻辑与,前面命令成功后才执行后面的命令
upgrade -y:升级所有已安装的软件包到最新版本,-y表示自动确认所有提示
为什么更新了很多东西
WSL的Ubuntu系统预装了很多基础软件包:


Python:系统自带Python,很多Linux工具依赖它
系统库:各种系统级别的库文件
开发工具:编译器、构建工具等
网络工具:curl、wget等
这是正常的吗?
是的,这是完全正常的。首次运行时通常会更新:


100-300个软件包
包括Python、系统库、安全补丁等
确保系统处于最新和安全的状态
对你的Java开发影响
更新这些包是好事:


确保系统安全性
获得最新的开发工具
为安装Java环境提供更好的基础

快速进入环境变量
编程技巧

按Win + R键,输入sysdm.cpl
选择"高级"选项卡 → "环境变量"

EF 先model生成到数据库
编程技巧
using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace WebNote.Migrations
{
    /// <inheritdoc />
    public partial class AddSiteOwnerTable : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "SiteOwners",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
                    Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
                    Bio = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
                    Resume = table.Column<string>(type: "nvarchar(max)", nullable: true),
                    AvatarPath = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: true),
                    Email = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
                    Mobile = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
                    GitHub = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
                    QQ = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
                    CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
                    UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
                    IsActive = table.Column<bool>(type: "bit", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_SiteOwners", x => x.Id);
                });
        }

        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "SiteOwners");
        }
    }
}

Add-Migration AddSiteOwnerTable

Update-Database

Host is not allowed to connect to this MySQL server 解决办法
编程技巧

最近研究mysql,遇到这个问题

解决方法,用navicat加上用户,以及ip

Json.ToJson乱码解决
编程技巧

  // 使用JsonConvert.SerializeObject保留中文
  var jsonSettings = new JsonSerializerSettings
  {
      StringEscapeHandling = StringEscapeHandling.Default
  };
  
  data.RequestBody = JsonConvert.SerializeObject(resultJson, jsonSettings);
  
  // 这里是关键 - 使用JsonConvert.SerializeObject来序列化整个data对象
  string postData = JsonConvert.SerializeObject(data, jsonSettings);
  
  // 使用序列化后的字符串而不是JSON.ToJSON(data)

 var content = HttpTools.PostHttpNHSHelp(url, postData, "同步答卷结果");

bundler压缩出现问题
编程技巧

问题出在函数声明的位置和压缩工具处理函数声明的方式上。
在JavaScript中,函数声明提升是很常见的,但是在压缩过程中,当函数声明放在代码中间而不是顶部时,可能会导致问题。

采用将 togglePanel 改为函数表达式的方法

document.addEventListener('DOMContentLoaded', function () {
    // 获取可折叠面板的标题元素
    const collapsibleHeader = document.querySelector('.collapsible-header');
    const collapseElement = document.getElementById('relatedNotesCollapse');
    const toggleIcon = document.querySelector('.toggle-icon');
    const mainContent = document.querySelector('.col-md-8');
    const sidePanel = document.querySelector('.col-md-4');
    const noteContent = document.querySelector('.note-content-detail');
    const floatBtn = document.querySelector('.float-toggle-btn');

    if (!collapsibleHeader || !collapseElement || !toggleIcon || !mainContent || !sidePanel) {
        // 如果找不到必要的元素,就退出
        return;
    }

    // 切换面板状态的函数(使用函数表达式而不是函数声明)
    const togglePanel = function() {
        const isExpanded = !sidePanel.classList.contains('collapsed');

        // 只在非移动设备上更新状态
        if (window.innerWidth >= 768) {
            // 更新本地存储中的状态
            localStorage.setItem('relatedNotesCollapsed', isExpanded);

            if (isExpanded) {
                // 收缩
                sidePanel.classList.add('collapsed');
                mainContent.classList.add('expanded');

                // 隐藏内容区域
                collapseElement.classList.remove('show');

                // 显示悬浮按钮
                if (floatBtn) {
                    floatBtn.classList.add('visible');
                }
            } else {
                // 展开
                sidePanel.classList.remove('collapsed');
                mainContent.classList.remove('expanded');

                // 显示内容区域
                collapseElement.classList.add('show');

                // 隐藏悬浮按钮
                if (floatBtn) {
                    floatBtn.classList.remove('visible');
                }
            }
        }
    };

    // 从本地存储中获取面板状态(如果有)
    const isCollapsed = localStorage.getItem('relatedNotesCollapsed') === 'true';

    // 初始化面板状态
    if (isCollapsed && window.innerWidth >= 768) {
        // 只在非移动设备上应用收缩状态
        // 收缩
        sidePanel.classList.add('collapsed');
        mainContent.classList.add('expanded');

        // 隐藏内容区域
        collapseElement.classList.remove('show');

        // 显示悬浮按钮
        if (floatBtn) {
            floatBtn.classList.add('visible');
        }
    }

    // 添加点击事件监听器,仅在非移动设备下启用
    collapsibleHeader.addEventListener('click', function (e) {
        // 在移动设备下阻止叉号图标的点击事件
        if (window.innerWidth < 768 && e.target.classList.contains('toggle-icon')) {
            e.stopPropagation();  // 阻止事件传播
            return;  // 在移动设备上不处理叉号图标点击
        }

        // 非移动设备下正常切换面板
        if (window.innerWidth >= 768) {
            togglePanel();
        }
    });

    // 特别为移动设备处理叉号图标
    if (toggleIcon) {
        toggleIcon.addEventListener('click', function (e) {
            // 在移动设备下阻止点击事件
            if (window.innerWidth < 768) {
                e.stopPropagation();
                e.preventDefault();
            }
        });
    }

    // 如果存在悬浮按钮,添加点击事件
    if (floatBtn) {
        floatBtn.addEventListener('click', function () {
            togglePanel();
        });
    }

    // 窗口大小改变时的处理
    window.addEventListener('resize', function () {
        if (window.innerWidth < 768) {
            // 在移动设备视图下恢复正常布局
            sidePanel.classList.remove('collapsed');
            mainContent.classList.remove('expanded');

            // 显示相关笔记内容
            collapseElement.classList.add('show');

            // 隐藏悬浮按钮
            if (floatBtn) {
                floatBtn.classList.remove('visible');
            }

            // 确保叉号图标在移动设备上不执行收缩功能
            if (toggleIcon) {
                toggleIcon.style.pointerEvents = 'none';
                toggleIcon.style.display = 'none';
            }
        } else {
            // 恢复图标功能
            if (toggleIcon) {
                toggleIcon.style.pointerEvents = 'auto';
                toggleIcon.style.display = '';
            }

            // 在桌面视图下,如果状态是收缩的,保持收缩
            if (localStorage.getItem('relatedNotesCollapsed') === 'true') {
                sidePanel.classList.add('collapsed');
                mainContent.classList.add('expanded');
                collapseElement.classList.remove('show');
                if (floatBtn) {
                    floatBtn.classList.add('visible');
                }
            }
        }
    });

    // 初始检查是否为移动设备,禁用叉号图标功能
    if (window.innerWidth < 768 && toggleIcon) {
        toggleIcon.style.pointerEvents = 'none';
        toggleIcon.style.display = 'none';
    }
});
解决弹出层乱码问题
编程技巧

是因为这里选了GB936出的错,改了以后好了

项目中添加 Bundler 来压缩 CSS 和 JS
编程技巧

添加 BuildBundlerMinifier NuGet 包

在项目根目录创建 bundleconfig.json 文件,用于配置要压缩和捆绑的文件

[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "wwwroot/css/site.css"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    }
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/js/site.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    }
  },
  {
    "outputFileName": "wwwroot/js/page-loader.min.js",
    "inputFiles": [
      "wwwroot/js/page-loader.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    }
  },
  {
    "outputFileName": "wwwroot/js/back-to-top.min.js",
    "inputFiles": [
      "wwwroot/js/back-to-top.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    }
  },
  {
    "outputFileName": "wwwroot/js/collapsible-panel.min.js",
    "inputFiles": [
      "wwwroot/js/collapsible-panel.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    }
  },
  {
    "outputFileName": "wwwroot/js/bundle.min.js",
    "inputFiles": [
      "wwwroot/js/site.js",
      "wwwroot/js/page-loader.js",
      "wwwroot/js/back-to-top.js"
    ],
    "minify": {
      "enabled": true,
      "renameLocals": true
    }
  }
]
@{
    // 获取当前页面路径
    var currentPage = ViewContext.RouteData.Values["page"]?.ToString() ?? "";
    var isPublicNotes = currentPage.StartsWith("/PublicNotes");
    var isPrivacy = currentPage.StartsWith("/Privacy");
    
    // 判断是否为开发环境
    var isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
    var cssFile = isDevelopment ? "~/css/site.css" : "~/css/site.min.css";
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - 个人研习知识记录笔记</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/lib/bootstrap-icons/bootstrap-icons.min.css" />
    <link rel="stylesheet" href="@cssFile" asp-append-version="true" />
    <link rel="stylesheet" href="~/WebNote.styles.css" asp-append-version="true" />
    @await RenderSectionAsync("Styles", required: false)
</head>
<body>
    <!-- 添加全局加载指示器 -->
    <div id="page-loader" class="page-loader">
        <div class="loader-container">
            <div class="spinner-border text-primary" role="status">
                <span class="visually-hidden">加载中...</span>
            </div>
            <p class="mt-3">页面加载中,请稍候...</p>
        </div>
    </div>
    <!-- 现有布局内容,包装在一个容器内以控制显示 -->
    <div id="page-content" class="page-content d-none">
        <!-- 页面内容 -->
    </div>
    
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    
    @if (isDevelopment)
    {
        <script src="~/js/site.js" asp-append-version="true"></script>
        <script src="~/js/page-loader.js" asp-append-version="true"></script>
        <script src="~/js/back-to-top.js" asp-append-version="true"></script>
    }
    else
    {
        <!-- 在生产环境中使用捆绑和压缩的JS文件 -->
        <script src="~/js/bundle.min.js" asp-append-version="true"></script>
    }
    
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>