feat(api): 添加用户管理和博文管理的RESTful API端点,添加基本的WebUI结构和样式,编写API文档说明,修复用户表密码字段类型

添加了处理用户认证、博文创建、读取、更新和删除的API端点。实现JWT认证机制,支持用户登录并进行相应的权限验证。同时,添加了数据库设置和模型定义,用于用户和博文的数据存储和管理。为项目添加了基本的WebUI结构,包括HTML、CSS和JavaScript文件,用于登录页面和404页面的展示。同时,配置了webui模块的项目文件,如`.editorconfig`和`.gitignore`。编写了API的使用文档,包括API的基本信息、请求和响应示例、错误处理等。支持分页查询、Markdown格式的博文内容存储。在数据库设置中,将用户表的密码字段更改为byte类型,以安全地存储经过bcrypt哈希处理的密码。确保在创建或更新用户时,密码得到正确的处理和存储。
This commit is contained in:
Wuqiyang312 2024-08-20 14:01:55 +08:00
parent 65dd1f3e64
commit 845876c633
36 changed files with 6086 additions and 3 deletions

7
.env Normal file
View File

@ -0,0 +1,7 @@
JWT_SECRET=8ThbmiozWCLvw3WCumWFEZW6RUxNZVmXjcETTaAotjX6Fh5tXxzPvHpZH12FUGUjTitsfB0vXEK1GGKcRUb2hAaTAisUehR6W7Ln
DB_TYPE=mysql # 可以是 "postgresql" 或 "mysql"
DB_HOST=wuqingyuan.mysql.rds.aliyuncs.com
DB_PORT=3306 # 对于 MySQL这里通常是 3306
DB_USER=blogdb
DB_PASSWORD=blogdb@123321
DB_NAME=blogdb

View File

@ -3,6 +3,7 @@
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/wqyblog_api_go.iml" filepath="$PROJECT_DIR$/wqyblog_api_go.iml" /> <module fileurl="file://$PROJECT_DIR$/wqyblog_api_go.iml" filepath="$PROJECT_DIR$/wqyblog_api_go.iml" />
<module fileurl="file://$PROJECT_DIR$/wqyblog_webui.iml" filepath="$PROJECT_DIR$/wqyblog_webui.iml" />
</modules> </modules>
</component> </component>
</project> </project>

172
README.md Normal file
View File

@ -0,0 +1,172 @@
# 博客 RESTful API 文档
## 简介
本 API 提供了一个用于管理博客文章的接口,支持使用 Markdown 格式存储内容,并且支持分页查询。后端基于 Go 语言开发,支持 MySQL 和 PostgreSQL 数据库。
## 基本信息
- **Base URL**: `https://func-e-wiwafluoqc.cn-shanghai.fcapp.run/api`
- **Content-Type**: `application/json`
- **Authorization**: 使用 JWT 进行身份验证。在所有需要身份验证的请求中,必须在请求头中包含 `Authorization: Bearer {token}`
## 测试认证密码
- **用户名**: `admin`
- **密码**: `123456@321`
## 认证
### 1. 用户登录
**描述**: 生成一个 JWT用于后续的 API 调用。
- **URL**: `/auth/login`
- **方法**: `POST`
- **请求体**:
```json
{
"username": "admin",
"password": "your_secure_password"
}
```
- **响应**:
```json
{
"token": "jwt_token"
}
```
- **说明**: 成功登录后,返回 JWT 令牌,用于后续请求的身份认证。
## 博文管理
### 2. 获取博文列表
**描述**: 获取博客文章列表,支持分页。
- **URL**: `/posts`
- **方法**: `GET`
- **请求参数**:
- `page` (可选): 页码,默认值为 `1`
- `limit` (可选): 每页显示的记录数量,默认值为 `10`
- **响应**:
```json
{
"page": 1,
"limit": 10,
"data": [
{
"ID": 1,
"CreatedAt": "2024-08-20T12:00:00Z",
"UpdatedAt": "2024-08-20T12:00:00Z",
"DeletedAt": null,
"Title": "First Post",
"Content": "Markdown content here..."
}
]
}
```
- **说明**: 返回分页后的博文列表。
### 3. 获取单个博文
**描述**: 根据 `id` 获取指定的博客文章。
- **URL**: `/posts/:id`
- **方法**: `GET`
- **响应**:
```json
{
"ID": 1,
"CreatedAt": "2024-08-20T12:00:00Z",
"UpdatedAt": "2024-08-20T12:00:00Z",
"DeletedAt": null,
"Title": "First Post",
"Content": "Markdown content here..."
}
```
- **说明**: 返回指定 `id` 的博文详情。如果该 `id` 对应的博文不存在,返回 `404` 错误。
### 4. 创建博文
**描述**: 创建一篇新的博客文章。
- **URL**: `/posts`
- **方法**: `POST`
- **请求体**:
```json
{
"title": "New Post Title",
"content": "Markdown content here..."
}
```
- **响应**:
```json
{
"ID": 3,
"CreatedAt": "2024-08-22T12:00:00Z",
"UpdatedAt": "2024-08-22T12:00:00Z",
"DeletedAt": null,
"Title": "New Post Title",
"Content": "Markdown content here..."
}
```
- **说明**: 创建一篇新的博文,并返回创建的博文信息。
### 5. 更新博文
**描述**: 更新指定 `id` 的博客文章。
- **URL**: `/posts/:id`
- **方法**: `PUT`
- **请求体**:
```json
{
"title": "Updated Post Title",
"content": "Updated markdown content..."
}
```
- **响应**:
```json
{
"ID": 1,
"CreatedAt": "2024-08-20T12:00:00Z",
"UpdatedAt": "2024-08-22T13:00:00Z",
"DeletedAt": null,
"Title": "Updated Post Title",
"Content": "Updated markdown content..."
}
```
- **说明**: 更新指定 `id` 的博文。如果该 `id` 对应的博文不存在,返回 `404` 错误。
### 6. 删除博文
**描述**: 删除指定 `id` 的博客文章。
- **URL**: `/posts/:id`
- **方法**: `DELETE`
- **响应**:
```json
{
"message": "Post deleted successfully"
}
```
- **说明**: 删除指定 `id` 的博文。如果该 `id` 对应的博文不存在,返回 `404` 错误。
## 错误处理
API 在发生错误时会返回以下格式的错误响应:
```json
{
"error": "Error description"
}
```
- **401 Unauthorized**: 需要有效的 JWT 令牌,但令牌未提供或无效。
- **404 Not Found**: 请求的资源不存在(如博文不存在)。
- **400 Bad Request**: 请求参数无效或缺失。
## 例外情况
在使用此 API 时,请确保使用 HTTPS 来保护数据传输的安全性。

68
db/setup.go Normal file
View File

@ -0,0 +1,68 @@
package db
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/joho/godotenv"
"golang.org/x/crypto/bcrypt"
"log"
"os"
"wqyblog_api_go/models"
)
var DB *gorm.DB
func Init() {
err := godotenv.Load()
if err != nil {
log.Println("Not found .env file. Only use system variables.")
}
dbType := os.Getenv("DB_TYPE")
dbUser := os.Getenv("DB_USER")
dbPassword := os.Getenv("DB_PASSWORD")
dbName := os.Getenv("DB_NAME")
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
var dbUri string
switch dbType {
case "mysql":
dbUri = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
dbUser, dbPassword, dbHost, dbPort, dbName)
case "postgresql":
dbUri = fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s",
dbHost, dbPort, dbUser, dbName, dbPassword)
default:
log.Fatalf("Unsupported DB_TYPE: %s", dbType)
}
DB, err = gorm.Open(dbType, dbUri)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
DB.AutoMigrate(&models.Post{})
DB.AutoMigrate(&models.User{})
log.Println("Database connected")
// 如果没有ADMIN用户
if DB.First(&models.User{}, "username = ?", "admin").RecordNotFound() {
log.Println("No ADMIN user found, creating one...")
// 添加ADMIN用户 密码使用 bcrypt 123456@321
password, err := bcrypt.GenerateFromPassword([]byte("123456@321"), bcrypt.DefaultCost)
if err != nil {
log.Fatalf("Failed to generate password: %v", err)
}
DB.Create(&models.User{
Username: "admin",
Password: password,
})
} else {
log.Println("ADMIN user found, skipping creation...")
}
}

42
go.mod
View File

@ -1,3 +1,43 @@
module wqyblog_api_go module wqyblog_api_go
go 1.19 go 1.23
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.10.0
github.com/jinzhu/gorm v1.9.16
github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.26.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.12.1 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.9.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

127
go.sum Normal file
View File

@ -0,0 +1,127 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24=
github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

9
handlers/hallo.go Normal file
View File

@ -0,0 +1,9 @@
package handlers
import "github.com/gin-gonic/gin"
func HalloWorld(context *gin.Context) {
context.JSON(200, gin.H{
"message": "Hello World",
})
}

118
handlers/login.go Normal file
View File

@ -0,0 +1,118 @@
package handlers
import (
"errors"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"net/http"
"os"
"time"
"wqyblog_api_go/db"
"wqyblog_api_go/models"
)
type LoginInput struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
func Login(c *gin.Context) {
var input LoginInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var user models.User
if err := db.DB.Where("username = ?", input.Username).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
return
}
// 验证密码
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(input.Password)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
return
}
// 生成 JWT 令牌
token, err := generateToken(user.Username)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not generate token"})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
}
var jwtKey = []byte(os.Getenv("JWT_SECRET"))
type Claims struct {
Username string `json:"username"`
jwt.StandardClaims
}
func generateToken(username string) (string, error) {
expirationTime := time.Now().Add(1 * time.Hour)
claims := &Claims{
Username: username,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
if err != nil {
return "", err
}
return tokenString, nil
}
func Authenticate(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Request does not contain an access token"})
c.Abort()
return
}
// Remove "Bearer " prefix if present
if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
tokenString = tokenString[7:]
}
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
if errors.Is(err, jwt.ErrSignatureInvalid) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token signature"})
c.Abort()
return
}
if ve, ok := err.(*jwt.ValidationError); ok && ve.Errors&jwt.ValidationErrorExpired != 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token is expired"})
c.Abort()
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": "Bad request"})
c.Abort()
return
}
if !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
c.Abort()
return
}
// Optionally, you can check the token's claims
c.Set("username", claims.Username)
c.Next()
}

89
handlers/posts.go Normal file
View File

@ -0,0 +1,89 @@
package handlers
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"wqyblog_api_go/db"
"wqyblog_api_go/models"
)
func GetPosts(c *gin.Context) {
// 获取查询参数
pageStr := c.DefaultQuery("page", "1")
limitStr := c.DefaultQuery("limit", "10")
// 将查询参数转换为整数
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
page = 1
}
limit, err := strconv.Atoi(limitStr)
if err != nil || limit < 1 {
limit = 10
}
// 计算偏移量
offset := (page - 1) * limit
var posts []models.Post
db.DB.Limit(limit).Offset(offset).Find(&posts)
c.JSON(http.StatusOK, gin.H{
"page": page,
"limit": limit,
"data": posts,
})
}
func GetPost(c *gin.Context) {
id := c.Param("id")
var post models.Post
if err := db.DB.First(&post, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
c.JSON(http.StatusOK, post)
}
func CreatePost(c *gin.Context) {
var input models.Post
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
post := models.Post{Title: input.Title, Content: input.Content}
db.DB.Create(&post)
c.JSON(http.StatusOK, post)
}
func UpdatePost(c *gin.Context) {
id := c.Param("id")
var post models.Post
if err := db.DB.First(&post, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
var input models.Post
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db.DB.Model(&post).Updates(models.Post{Title: input.Title, Content: input.Content})
c.JSON(http.StatusOK, post)
}
func DeletePost(c *gin.Context) {
id := c.Param("id")
var post models.Post
if err := db.DB.First(&post, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
return
}
db.DB.Delete(&post)
c.JSON(http.StatusOK, gin.H{"message": "Post deleted successfully"})
}

BIN
main Executable file

Binary file not shown.

30
main.go
View File

@ -1,7 +1,33 @@
package main package main
import "fmt" import (
"github.com/gin-gonic/gin"
"wqyblog_api_go/db"
"wqyblog_api_go/handlers"
)
func main() { func main() {
fmt.Println("Hello, World!") db.Init()
r := gin.Default()
r.GET("/", handlers.HalloWorld)
r.POST("/api/auth/login", handlers.Login)
r.GET("/api/posts", handlers.GetPosts)
r.GET("/api/posts/:id", handlers.GetPost)
authorized := r.Group("/")
authorized.Use(handlers.Authenticate)
{
authorized.POST("/api/posts", handlers.CreatePost)
authorized.PUT("/api/posts/:id", handlers.UpdatePost)
authorized.DELETE("/api/posts/:id", handlers.DeletePost)
}
err := r.Run(":8080")
if err != nil {
return
}
} }

11
models/post.go Normal file
View File

@ -0,0 +1,11 @@
package models
import (
"github.com/jinzhu/gorm"
)
type Post struct {
gorm.Model
Title string `json:"title"`
Content string `json:"content"`
}

11
models/user.go Normal file
View File

@ -0,0 +1,11 @@
package models
import (
"github.com/jinzhu/gorm"
)
type User struct {
gorm.Model
Username string `json:"username"`
Password []byte `json:"password"`
}

22
text.http Normal file
View File

@ -0,0 +1,22 @@
POST http://localhost:8080/api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "123456@321"
}
###
POST http://localhost:8080/api/posts
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzI0MTMxNjIxfQ.NZu5hhl7XjT9BdjIPmBAknekNHIavZYh5Xzz39PUzkk
{
"title": "New Post Title",
"content": "Markdown content here..."
}
###
GET http://localhost:8080/api/posts
Content-Type: application/json

8
wqyblog_webui.iml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/wqyblog_webui" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,11 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

194
wqyblog_webui/.gitattributes vendored Normal file
View File

@ -0,0 +1,194 @@
## GITATTRIBUTES FOR WEB PROJECTS
#
# These settings are for any web project.
#
# Details per file setting:
# text These files should be normalized (i.e. convert CRLF to LF).
# binary These files are binary and should be left untouched.
#
# Note that binary is a macro for -text -diff.
######################################################################
## AUTO-DETECT
## Handle line endings automatically for files detected as
## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below.
* text=auto
## SOURCE CODE
*.bat text eol=crlf
*.coffee text
*.css text
*.htm text
*.html text
*.inc text
*.ini text
*.js text
*.json text
*.jsx text
*.less text
*.od text
*.onlydata text
*.php text
*.pl text
*.py text
*.rb text
*.sass text
*.scm text
*.scss text
*.sh text eol=lf
*.sql text
*.styl text
*.tag text
*.ts text
*.tsx text
*.xml text
*.xhtml text
## DOCKER
*.dockerignore text
Dockerfile text
## DOCUMENTATION
*.markdown text
*.md text
*.mdwn text
*.mdown text
*.mkd text
*.mkdn text
*.mdtxt text
*.mdtext text
*.txt text
AUTHORS text
CHANGELOG text
CHANGES text
CONTRIBUTING text
COPYING text
copyright text
*COPYRIGHT* text
INSTALL text
license text
LICENSE text
NEWS text
readme text
*README* text
TODO text
## TEMPLATES
*.dot text
*.ejs text
*.haml text
*.handlebars text
*.hbs text
*.hbt text
*.jade text
*.latte text
*.mustache text
*.njk text
*.phtml text
*.tmpl text
*.tpl text
*.twig text
## LINTERS
.babelrc text
.csslintrc text
.eslintrc text
.htmlhintrc text
.jscsrc text
.jshintrc text
.jshintignore text
.prettierrc text
.stylelintrc text
## CONFIGS
*.bowerrc text
*.cnf text
*.conf text
*.config text
.browserslistrc text
.editorconfig text
.gitattributes text
.gitconfig text
.gitignore text
.htaccess text
*.npmignore text
*.yaml text
*.yml text
browserslist text
Makefile text
makefile text
## HEROKU
Procfile text
.slugignore text
## GRAPHICS
*.ai binary
*.bmp binary
*.eps binary
*.gif binary
*.ico binary
*.jng binary
*.jp2 binary
*.jpg binary
*.jpeg binary
*.jpx binary
*.jxr binary
*.pdf binary
*.png binary
*.psb binary
*.psd binary
*.svg text
*.svgz binary
*.tif binary
*.tiff binary
*.wbmp binary
*.webp binary
## AUDIO
*.kar binary
*.m4a binary
*.mid binary
*.midi binary
*.mp3 binary
*.ogg binary
*.ra binary
## VIDEO
*.3gpp binary
*.3gp binary
*.as binary
*.asf binary
*.asx binary
*.fla binary
*.flv binary
*.m4v binary
*.mng binary
*.mov binary
*.mp4 binary
*.mpeg binary
*.mpg binary
*.ogv binary
*.swc binary
*.swf binary
*.webm binary
## ARCHIVES
*.7z binary
*.gz binary
*.jar binary
*.rar binary
*.tar binary
*.zip binary
## FONTS
*.ttf binary
*.eot binary
*.otf binary
*.woff binary
*.woff2 binary
## EXECUTABLES
*.exe binary
*.pyc binary

6
wqyblog_webui/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Include your project-specific ignores in this file
# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
# Useful .gitignore templates: https://github.com/github/gitignore
node_modules
dist
.cache

62
wqyblog_webui/404.html Normal file
View File

@ -0,0 +1,62 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>找不到页面</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
line-height: 1.2;
margin: 0;
}
html {
color: #888;
display: table;
font-family: sans-serif;
height: 100%;
text-align: center;
width: 100%;
}
body {
display: table-cell;
vertical-align: middle;
margin: 2em auto;
}
h1 {
color: #555;
font-size: 2em;
font-weight: 400;
}
p {
margin: 0 auto;
width: 280px;
}
@media only screen and (max-width: 280px) {
body,
p {
width: 95%;
}
h1 {
font-size: 1.5em;
margin: 0 0 0.3em;
}
}
</style>
</head>
<body>
<h1>找不到页面</h1>
<p>抱歉,您尝试查看的页面不存在。</p>
</body>
</html>
<!-- IE needs 512+ bytes: https://docs.microsoft.com/archive/blogs/ieinternals/friendly-http-error-pages -->

19
wqyblog_webui/LICENSE.txt Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) HTML5 Boilerplate
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

220
wqyblog_webui/css/style.css Normal file
View File

@ -0,0 +1,220 @@
html {
color: #222;
font-size: 1em;
line-height: 1.4;
}
::-moz-selection {
background: #b3d4fc;
text-shadow: none;
}
::selection {
background: #b3d4fc;
text-shadow: none;
}
/*
* 更好看的默认水平线
*/
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
/*
* 删除音频画布iframe 之间的间隙
* 图像视频及其容器底部
* https://github.com/h5bp/html5-boilerplate/issues/440
*/
audio,
canvas,
iframe,
img,
svg,
video {
vertical-align: middle;
}
/*
* 删除默认字段集样式
*/
fieldset {
border: 0;
margin: 0;
padding: 0;
}
/*
* 仅允许垂直调整文本区域的大小
*/
textarea {
resize: vertical;
}
/* ==========================================================================
自定义样式
========================================================================== */
body {
margin: 0;
padding: 0;
}
.container {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background-color: #f5f5f5;
margin-bottom: 1em;
width: 100vw;
box-shadow: 0 0 1em rgba(0, 0, 0, 0.1);
}
.header-title {
text-align: center;
padding: 1em;
font-size: 1.5rem;
margin: 0;
font-weight: bold;
color: #333;
}
/* ==========================================================================
辅助类
========================================================================== */
.hidden,
[hidden] {
display: none !important;
}
.visually-hidden {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
/* 1 */
}
.visually-hidden.focusable:active,
.visually-hidden.focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
white-space: inherit;
width: auto;
}
.invisible {
visibility: hidden;
}
.clearfix::before,
.clearfix::after {
content: "";
display: table;
}
.clearfix::after {
clear: both;
}
@media only screen and (min-width: 35em) {
/* 对满足条件的视口进行样式调整 */
}
@media print,
(-webkit-min-device-pixel-ratio: 1.25),
(min-resolution: 1.25dppx),
(min-resolution: 120dpi) {
/* 针对高分辨率设备的风格调整 */
}
/* ==========================================================================
打印样式
内联以避免额外的 HTTP 请求
========================================================================== */
@media print {
*,
*::before,
*::after {
background: #fff !important;
color: #000 !important;
/* Black prints faster */
box-shadow: none !important;
text-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]::after {
content: " (" attr(href) ")";
}
abbr[title]::after {
content: " (" attr(title) ")";
}
/*
* Don't show links that are fragment identifiers,
* or use the `javascript:` pseudo protocol
*/
a[href^="#"]::after,
a[href^="javascript:"]::after {
content: "";
}
pre {
white-space: pre-wrap !important;
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
tr,
img {
page-break-inside: avoid;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
}

BIN
wqyblog_webui/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
wqyblog_webui/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

1
wqyblog_webui/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 192 192"><path fill="#e08524" d="M75.3 73.4H18.4l45.3 34.3L48.3 163l46.1-32.3 48.2 34.6-16.9-58.3 44.9-33.6H115l-20.5-55-19.2 55z"/><path d="m96.7 18.8 18.2 8.2 16.5 44.3h-15.1L96.7 18.8zm-47 146 18.7 9.9 42.6-29.9-16.5-11.4-44.8 31.4zm79.1-56.8 17.4 9.4 18.6 60.1-19.7-11.3-16.3-58.2z"/><path d="m173.1 74.3 17.8 9.2-44.7 34-17.4-9.4 44.3-33.8z"/></svg>

After

Width:  |  Height:  |  Size: 429 B

View File

49
wqyblog_webui/index.html Normal file
View File

@ -0,0 +1,49 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<link rel="stylesheet" href="./css/style.css">
<meta name="description" content="">
<meta property="og:title" content="">
<meta property="og:type" content="">
<meta property="og:url" content="">
<meta property="og:image" content="">
<meta property="og:image:alt" content="">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="icon.png">
<link rel="manifest" href="site.webmanifest">
<meta name="theme-color" content="#fafafa">
</head>
<body>
<div class="container">
<div class="header">
<div class="header-title">
WqyBlog.cn
</div>
<div class="login">
<a href="login.html">登录</a>
</div>
</div>
<div class="main">
<div class="main-content">
</div>
<div class="main-sidebar">
</div>
</div>
</div>
<script src="jquery/dist/jquery.js"></script>
<script src="./js/app.js"></script>
</body>
</html>

1
wqyblog_webui/js/app.js Normal file
View File

@ -0,0 +1 @@
// app.js wqyblog.cn

0
wqyblog_webui/js/vendor/.gitkeep vendored Normal file
View File

26
wqyblog_webui/login.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<link rel="stylesheet" href="./css/style.css">
<meta name="description" content="">
<meta property="og:title" content="">
<meta property="og:type" content="">
<meta property="og:url" content="">
<meta property="og:image" content="">
<meta property="og:image:alt" content="">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="icon.png">
<link rel="manifest" href="site.webmanifest">
<meta name="theme-color" content="#fafafa">
</head>
<body>
</body>
</html>

4690
wqyblog_webui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
{
"name": " ",
"version": "0.0.1",
"description": "",
"private": true,
"keywords": [
""
],
"license": "",
"author": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack serve --open --config webpack.config.dev.js",
"build": "webpack --config webpack.config.prod.js"
},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"html-webpack-plugin": "^5.6.0",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",
"webpack-merge": "^5.10.0"
},
"dependencies": {
"jquery": "^3.7.1"
}
}

5
wqyblog_webui/robots.txt Normal file
View File

@ -0,0 +1,5 @@
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow:

View File

@ -0,0 +1,12 @@
{
"short_name": "",
"name": "",
"icons": [{
"src": "icon.png",
"type": "image/png",
"sizes": "192x192"
}],
"start_url": "/?utm_source=homescreen",
"background_color": "#fafafa",
"theme_color": "#fafafa"
}

View File

@ -0,0 +1,12 @@
const path = require('path');
module.exports = {
entry: {
app: './js/app.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
filename: './js/app.js',
},
};

View File

@ -0,0 +1,13 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
liveReload: true,
hot: true,
open: true,
static: ['./'],
},
});

View File

@ -0,0 +1,26 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
}),
new CopyPlugin({
patterns: [
{ from: 'img', to: 'img' },
{ from: 'css', to: 'css' },
{ from: 'js/vendor', to: 'js/vendor' },
{ from: 'icon.svg', to: 'icon.svg' },
{ from: 'favicon.ico', to: 'favicon.ico' },
{ from: 'robots.txt', to: 'robots.txt' },
{ from: 'icon.png', to: 'icon.png' },
{ from: '404.html', to: '404.html' },
{ from: 'site.webmanifest', to: 'site.webmanifest' },
],
}),
],
});