5 Commits d8ad850fc6 ... 6d9ec7b5aa

Autor SHA1 Nachricht Datum
  runningwater 6d9ec7b5aa feat: cache 包 vor 3 Monaten
  runningwater 16a1b2bbeb feat: 友情链接列表 vor 3 Monaten
  runningwater 03ea0a8a8b feat: 显示话题 vor 3 Monaten
  runningwater 0a6db82f1b feat: 话题列表 vor 3 Monaten
  runningwater 9d3f024705 feat: 删除话题 vor 3 Monaten

+ 5 - 0
README.md

@@ -76,6 +76,11 @@ UNIQUE KEY `migration` (`migration`)
 
 #### 🚀 新功能
 
+- 显示话题
+- 话题列表
+- 删除话题
+- *(command)* Make policy 命令
+- 授权策略
 - 话题更新接口
 - 创建话题接口
 - 话题模型和迁移

+ 2 - 1
app/cmd/make/tpls/seeder.tpl

@@ -3,11 +3,12 @@ package seeders
 import (
 	"fmt"
 
+	"gorm.io/gorm"
+
 	"github.com/runningwater/gohub/database/factories"
 	"github.com/runningwater/gohub/pkg/console"
 	"github.com/runningwater/gohub/pkg/logger"
 	"github.com/runningwater/gohub/pkg/seed"
-	"gorm.io/gorm"
 )
 
 func init() {

+ 17 - 0
app/http/controllers/api/v1/links_controller.go

@@ -0,0 +1,17 @@
+package v1
+
+import (
+	"github.com/runningwater/gohub/app/models/link"
+	"github.com/runningwater/gohub/pkg/response"
+
+	"github.com/gin-gonic/gin"
+)
+
+type LinksController struct {
+	BaseApiController
+}
+
+func (ctrl *LinksController) Index(c *gin.Context) {
+	links := link.All()
+	response.Data(c, links)
+}

+ 41 - 4
app/http/controllers/api/v1/topics_controller.go

@@ -14,10 +14,28 @@ type TopicsController struct {
 	BaseApiController
 }
 
-// func (ctrl *TopicsController) Index(c *gin.Context) {
-//     topics := topic.All()
-//     response.Data(c, topics)
-// }
+func (ctrl *TopicsController) Show(c *gin.Context) {
+	topicModel := topic.Get(c.Param("id"))
+	if topicModel.ID == 0 {
+		response.Abort404(c)
+		return
+	}
+	response.Data(c, topicModel)
+}
+
+func (ctrl *TopicsController) Index(c *gin.Context) {
+	request := requests.PaginationRequest{}
+	if ok := requests.Validate(c, &request, requests.Pagination); !ok {
+		return
+	}
+
+	data, pager := topic.Paginate(c, 10)
+
+	response.JSON(c, gin.H{
+		"data":  data,
+		"pager": pager,
+	})
+}
 
 func (ctrl *TopicsController) Store(c *gin.Context) {
 
@@ -69,4 +87,23 @@ func (ctrl *TopicsController) Update(c *gin.Context) {
 }
 
 func (ctrl *TopicsController) Delete(c *gin.Context) {
+
+	topicModel := topic.Get(c.Param("id"))
+	if topicModel.ID == 0 {
+		response.Abort404(c)
+		return
+	}
+
+	if !policies.CanModifyTopic(c, topicModel) {
+		response.Abort403(c)
+		return
+	}
+
+	rowsAffected := topicModel.Delete()
+	if rowsAffected > 0 {
+		response.Success(c)
+		return
+	}
+
+	response.Abort500(c, "删除失败,请稍后尝试~")
 }

+ 11 - 0
app/models/link/link_hooks.go

@@ -0,0 +1,11 @@
+package link
+
+// func (link *Link) BeforeSave(tx *gorm.DB) (err error) {}
+// func (link *Link) BeforeCreate(tx *gorm.DB) (err error) {}
+// func (link *Link) AfterCreate(tx *gorm.DB) (err error) {}
+// func (link *Link) BeforeUpdate(tx *gorm.DB) (err error) {}
+// func (link *Link) AfterUpdate(tx *gorm.DB) (err error) {}
+// func (link *Link) AfterSave(tx *gorm.DB) (err error) {}
+// func (link *Link) BeforeDelete(tx *gorm.DB) (err error) {}
+// func (link *Link) AfterDelete(tx *gorm.DB) (err error) {}
+// func (link *Link) AfterFind(tx *gorm.DB) (err error) {}

+ 30 - 0
app/models/link/link_model.go

@@ -0,0 +1,30 @@
+// Package link 模型
+package link
+
+import (
+	"github.com/runningwater/gohub/app/models"
+	"github.com/runningwater/gohub/pkg/database"
+)
+
+type Link struct {
+	models.BaseModel
+
+	Name string `json:"name,omitempty"`
+	URL  string `json:"url,omitempty"`
+
+	models.CommonTimestampsField
+}
+
+func (link *Link) Create() {
+	database.DB.Create(&link)
+}
+
+func (link *Link) Save() (rowsAffected int64) {
+	result := database.DB.Save(&link)
+	return result.RowsAffected
+}
+
+func (link *Link) Delete() (rowsAffected int64) {
+	result := database.DB.Delete(&link)
+	return result.RowsAffected
+}

+ 42 - 0
app/models/link/link_util.go

@@ -0,0 +1,42 @@
+package link
+
+import (
+	"github.com/gin-gonic/gin"
+
+	"github.com/runningwater/gohub/pkg/app"
+	"github.com/runningwater/gohub/pkg/database"
+	"github.com/runningwater/gohub/pkg/paginator"
+)
+
+func Get(idStr string) (link Link) {
+	database.DB.Where("id", idStr).First(&link)
+	return
+}
+
+func GetBy(field, value string) (link Link) {
+	database.DB.Where("? = ?", field, value).First(&link)
+	return
+}
+
+func All() (links []Link) {
+	database.DB.Find(&links)
+	return
+}
+
+func IsExist(field, value string) bool {
+	var count int64
+	database.DB.Model(Link{}).Where("? = ?", field, value).Count(&count)
+	return count > 0
+}
+
+// Paginate 分页内容
+func Paginate(c *gin.Context, pageSize int) (links []Link, paging paginator.Paging) {
+	paging = paginator.Paginate(
+		c,
+		database.DB.Model(Link{}),
+		&links,
+		app.V1URL(database.TableName(&Link{})),
+		pageSize,
+	)
+	return
+}

+ 2 - 1
app/models/topic/topic_util.go

@@ -2,6 +2,7 @@ package topic
 
 import (
 	"github.com/gin-gonic/gin"
+	"gorm.io/gorm/clause"
 
 	"github.com/runningwater/gohub/pkg/app"
 	"github.com/runningwater/gohub/pkg/database"
@@ -9,7 +10,7 @@ import (
 )
 
 func Get(idStr string) (topic Topic) {
-	database.DB.Where("id", idStr).First(&topic)
+	database.DB.Preload(clause.Associations).Where("id", idStr).First(&topic)
 	return
 }
 

+ 23 - 0
bootstrap/cache.go

@@ -0,0 +1,23 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/7/21 12:42
+// Desc:
+
+package bootstrap
+
+import (
+	"fmt"
+
+	"github.com/runningwater/gohub/pkg/cache"
+	"github.com/runningwater/gohub/pkg/config"
+)
+
+func SetupCache() {
+	// 初始化缓存的专用 redis client
+	rds := cache.NewRedisStore(
+		fmt.Sprintf("%v:%v", config.GetString("redis.host"), config.GetString("redis.port")),
+		config.GetString("redis.username"),
+		config.GetString("redis.password"),
+		config.GetInt("redis.database_cache"),
+	)
+	cache.InitWithCacheStore(rds)
+}

+ 2 - 0
config/redis.go

@@ -12,6 +12,8 @@ func init() {
 
 			// 业务类型存储使用 1(图片验证码、短信验证码、会话)
 			"database": config.Env("REDIS_DATABASE", 1),
+			// 缓存 cache 包使用 0, 缓存清空应不影响业务
+			"database_cache": config.Env("REDIS_CACHE_DB", 0),
 		}
 	})
 }

+ 24 - 0
database/factories/link_factory.go

@@ -0,0 +1,24 @@
+// Package factories 存放 link 工厂方法
+package factories
+
+import (
+	"github.com/bxcodec/faker/v4"
+
+	"github.com/runningwater/gohub/app/models/link"
+)
+
+func MakeLinks(times int) []link.Link {
+	var objs []link.Link
+
+	// 设置唯一值, 如 Link 模型中的某个字段需要唯一
+	faker.SetGenerateUniqueValues(true)
+
+	for range times {
+		model := link.Link{
+			Name: faker.Username(),
+			URL:  faker.URL(),
+		}
+		objs = append(objs, model)
+	}
+	return objs
+}

+ 26 - 0
database/factories/topic_factory.go

@@ -0,0 +1,26 @@
+// Package factories 存放 topic 工厂方法
+package factories
+
+import (
+	"github.com/bxcodec/faker/v4"
+
+	"github.com/runningwater/gohub/app/models/topic"
+)
+
+func MakeTopics(times int) []topic.Topic {
+	var objs []topic.Topic
+
+	// 设置唯一值, 如 Topic 模型中的某个字段需要唯一
+	faker.SetGenerateUniqueValues(true)
+
+	for range times {
+		model := topic.Topic{
+			Title:      faker.Sentence(),
+			Body:       faker.Paragraph(),
+			UserID:     "1",
+			CategoryID: "3",
+		}
+		objs = append(objs, model)
+	}
+	return objs
+}

+ 30 - 0
database/migrations/2025_07_21_091558_add_links_table.go

@@ -0,0 +1,30 @@
+package migrations
+
+import (
+	"gorm.io/gorm"
+
+	"github.com/runningwater/gohub/app/models"
+	"github.com/runningwater/gohub/pkg/migrate"
+)
+
+func init() {
+
+	type Link struct {
+		models.BaseModel
+
+		Name string `gorm:"type:varchar(255);not null"`
+		URL  string `gorm:"type:varchar(255);default:null"`
+
+		models.CommonTimestampsField
+	}
+
+	up := func(migrator gorm.Migrator, DB *gorm.DB) {
+		_ = DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci comment='友情链接'").AutoMigrate(&Link{})
+	}
+
+	down := func(migrator gorm.Migrator, DB *gorm.DB) {
+		_ = migrator.DropTable(&Link{})
+	}
+
+	migrate.Add(up, down, "2025_07_21_091558_add_links_table")
+}

+ 2 - 0
database/seeders/init.go

@@ -10,5 +10,7 @@ func Initialize() {
 	seed.SetRunOrder([]string{
 		"UsersTableSeeder",
 		"CategoriesTableSeeder",
+		"TopicsTableSeeder",
+		"LinksTableSeeder",
 	})
 }

+ 31 - 0
database/seeders/link_seeder.go

@@ -0,0 +1,31 @@
+package seeders
+
+import (
+	"fmt"
+
+	"gorm.io/gorm"
+
+	"github.com/runningwater/gohub/database/factories"
+	"github.com/runningwater/gohub/pkg/console"
+	"github.com/runningwater/gohub/pkg/logger"
+	"github.com/runningwater/gohub/pkg/seed"
+)
+
+func init() {
+	// 添加 Seeder
+	seed.Add("LinksTableSeeder", func(db *gorm.DB) {
+		// 创建 10 个用户对象
+		links := factories.MakeLinks(5)
+
+		// 批量插入到数据库
+		result := db.Table("links").Create(&links)
+
+		if err := result.Error; err != nil {
+			logger.LogIf(err)
+			return
+		}
+
+		// 打印成功信息
+		console.Success(fmt.Sprintf("Table [%v] %v rows seeded", result.Statement.Table, result.RowsAffected))
+	})
+}

+ 31 - 0
database/seeders/topic_seeder.go

@@ -0,0 +1,31 @@
+package seeders
+
+import (
+	"fmt"
+
+	"gorm.io/gorm"
+
+	"github.com/runningwater/gohub/database/factories"
+	"github.com/runningwater/gohub/pkg/console"
+	"github.com/runningwater/gohub/pkg/logger"
+	"github.com/runningwater/gohub/pkg/seed"
+)
+
+func init() {
+	// 添加 Seeder
+	seed.Add("TopicsTableSeeder", func(db *gorm.DB) {
+		// 创建 10 个用户对象
+		topics := factories.MakeTopics(10)
+
+		// 批量插入到数据库
+		result := db.Table("topics").Create(&topics)
+
+		if err := result.Error; err != nil {
+			logger.LogIf(err)
+			return
+		}
+
+		// 打印成功信息
+		console.Success(fmt.Sprintf("Table [%v] %v rows seeded", result.Statement.Table, result.RowsAffected))
+	})
+}

+ 21 - 0
gohub.http

@@ -146,3 +146,24 @@ Content-Type: application/json
   "body": "话题1内容, 这里是帖子描述内容帖子描述内容",
   "category_id": "3"
 }
+
+### 显示话题
+GET {{base_url}}/v1/topics/2 HTTP/1.1
+Content-Type: application/json
+
+### 话题列表
+GET {{base_url}}/v1/topics HTTP/1.1
+Content-Type: application/json
+
+### 删除话题
+DELETE {{base_url}}/v1/topics/1 HTTP/1.1
+Authorization: Bearer {{access_token}}
+Content-Type: application/json
+
+### 话题列表
+GET {{base_url}}/v1/topics HTTP/1.1
+Content-Type: application/json
+
+### 友情链接列表
+GET {{base_url}}/v1/links HTTP/1.1
+Content-Type: application/json

+ 3 - 0
main.go

@@ -41,6 +41,9 @@ func main() {
 
 			// 初始化 Redis
 			bootstrap.SetupRedis()
+
+			// 初始化 Cache
+			bootstrap.SetupCache()
 		},
 	}
 

+ 61 - 0
pkg/cache/RedisStore.go

@@ -0,0 +1,61 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/7/21 09:56
+// Desc: redis 缓存,实现 Store 接口
+
+package cache
+
+import (
+	"time"
+
+	"github.com/runningwater/gohub/pkg/config"
+	"github.com/runningwater/gohub/pkg/redis"
+)
+
+type RedisStore struct {
+	RedisClient *redis.Client
+	KeyPrefix   string
+}
+
+func NewRedisStore(address, username, password string, db int) *RedisStore {
+	rs := &RedisStore{
+		RedisClient: redis.NewClient(address, username, password, db),
+		KeyPrefix:   config.GetString("app.name") + ":cache:",
+	}
+	return rs
+}
+
+func (r *RedisStore) Set(key, value string, expire time.Duration) {
+	r.RedisClient.Set(r.KeyPrefix+key, value, expire)
+}
+
+func (r *RedisStore) Get(key string) string {
+	return r.RedisClient.Get(r.KeyPrefix + key)
+}
+
+func (r *RedisStore) Has(key string) bool {
+	return r.RedisClient.Has(r.KeyPrefix + key)
+}
+
+func (r *RedisStore) Forget(key string) {
+	r.RedisClient.Del(r.KeyPrefix + key)
+}
+
+func (r *RedisStore) Forever(key, value string) {
+	r.RedisClient.Set(r.KeyPrefix+key, value, 0)
+}
+
+func (r *RedisStore) Flush() {
+	r.RedisClient.FlushDb()
+}
+
+func (r *RedisStore) IsAlive() error {
+	return r.RedisClient.Ping()
+}
+
+func (r *RedisStore) Increment(params ...any) {
+	r.RedisClient.Increment(params)
+}
+
+func (r *RedisStore) Decrement(params ...any) {
+	r.RedisClient.Decrement(params)
+}

+ 152 - 0
pkg/cache/cache.go

@@ -0,0 +1,152 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/7/21 10:10
+// Desc: 缓存包
+
+package cache
+
+import (
+	"encoding/json"
+	"sync"
+	"time"
+
+	"github.com/spf13/cast"
+
+	"github.com/runningwater/gohub/pkg/logger"
+)
+
+type Service struct {
+	Store Store
+}
+
+var once sync.Once
+var Cache *Service
+
+func InitWithCacheStore(store Store) {
+	once.Do(func() {
+		Cache = &Service{
+			Store: store,
+		}
+	})
+}
+
+func Set(key string, value any, expire time.Duration) {
+	bytes, err := json.Marshal(value)
+	logger.LogIf(err)
+	Cache.Store.Set(key, string(bytes), expire)
+}
+
+func Get(key string) any {
+	stringValue := Cache.Store.Get(key)
+	var wanted any
+	err := json.Unmarshal([]byte(stringValue), &wanted)
+	logger.LogIf(err)
+	return wanted
+}
+
+func Has(key string) bool {
+	return Cache.Store.Has(key)
+}
+
+// GetObject 获取对象,用法如下:
+//
+//	model := user.User{}
+//	cache.GetObject("user:1", &model)
+func GetObject(key string, wanted any) {
+	val := Cache.Store.Get(key)
+	if len(val) > 0 {
+		err := json.Unmarshal([]byte(val), &wanted)
+		logger.LogIf(err)
+	} else {
+		logger.WarnString("cache", "GetObject", "cache key ["+key+"] not found")
+	}
+}
+
+func GetString(key string) string {
+	return cast.ToString(Get(key))
+}
+
+func GetBool(key string) bool {
+	return cast.ToBool(Get(key))
+}
+
+func GetInt(key string) int {
+	return cast.ToInt(Get(key))
+}
+
+func GetInt32(key string) int32 {
+	return cast.ToInt32(Get(key))
+}
+
+func GetInt64(key string) int64 {
+	return cast.ToInt64(Get(key))
+}
+
+func GetUint(key string) uint {
+	return cast.ToUint(Get(key))
+}
+
+func GetUint32(key string) uint32 {
+	return cast.ToUint32(Get(key))
+}
+
+func GetUint64(key string) uint64 {
+	return cast.ToUint64(Get(key))
+}
+
+func GetFloat32(key string) float32 {
+	return cast.ToFloat32(Get(key))
+}
+
+func GetFloat64(key string) float64 {
+	return cast.ToFloat64(Get(key))
+}
+
+func GetTime(key string) time.Time {
+	return cast.ToTime(Get(key))
+}
+
+func GetDuration(key string) time.Duration {
+	return cast.ToDuration(Get(key))
+}
+func GetIntSlice(key string) []int {
+	return cast.ToIntSlice(Get(key))
+}
+func GetStringSlice(key string) []string {
+	return cast.ToStringSlice(Get(key))
+}
+func GetStringMap(key string) map[string]string {
+	return cast.ToStringMapString(Get(key))
+}
+func GetStringMapString(key string) map[string]string {
+	return cast.ToStringMapString(Get(key))
+}
+func GetStringMapStringSlice(key string) map[string][]string {
+	return cast.ToStringMapStringSlice(Get(key))
+}
+func GetStringMapInt(key string) map[string]int {
+	return cast.ToStringMapInt(Get(key))
+}
+
+func Forget(key string) {
+	Cache.Store.Forget(key)
+}
+
+func Forever(key string, value string) {
+	Cache.Store.Set(key, value, 0)
+}
+
+func Flush() {
+	Cache.Store.Flush()
+}
+
+func Increment(params ...any) {
+	Cache.Store.Increment(params...)
+}
+
+func Decrement(params ...any) {
+	Cache.Store.Decrement(params...)
+}
+
+func IsAlive() error {
+	return Cache.Store.IsAlive()
+}

+ 23 - 0
pkg/cache/store_interface.go

@@ -0,0 +1,23 @@
+// Package cache 系统缓存包
+package cache
+
+import (
+	"time"
+)
+
+type Store interface {
+	Set(key, value string, expire time.Duration)
+	Get(key string) string
+	Has(key string) bool
+	Forget(key string)
+	Forever(key, value string)
+	Flush()
+
+	IsAlive() error
+
+	// Increment 自增
+	//  当参数只有1个时,为key 增加1
+	//  当参数有2个时,为key 增加值
+	Increment(params ...any)
+	Decrement(params ...any)
+}

+ 1 - 1
pkg/captcha/store_redis.go

@@ -12,7 +12,7 @@ import (
 // RedisStore is a store for captcha using Redis.
 // It implements the Save interface. base64Captcha.Store interface.
 type RedisStore struct {
-	RedisClient *redis.RedisClient
+	RedisClient *redis.Client
 	KeyPrefix   string
 }
 

+ 1 - 1
pkg/logger/logger.go

@@ -1,4 +1,4 @@
-// Pacage logger provides a simple wrapper around zap logger
+// Package logger provides a simple wrapper around zap logger
 package logger
 
 import (

+ 17 - 15
pkg/redis/redis.go

@@ -3,29 +3,31 @@ package redis
 
 import (
 	"context"
+	"errors"
 	"sync"
 	"time"
 
 	"github.com/redis/go-redis/v9"
+
 	"github.com/runningwater/gohub/pkg/logger"
 )
 
-// RedisClient Redis 服务
-type RedisClient struct {
+// Client Redis 服务
+type Client struct {
 	Client  *redis.Client
 	Context context.Context
 }
 
 // Ping 测试 Redis 连接
 // 通过 Ping 方法测试 Redis 连接是否正常
-func (rds *RedisClient) Ping() error {
+func (rds *Client) Ping() error {
 	_, err := rds.Client.Ping(rds.Context).Result()
 	return err
 }
 
 // Set 设置 Redis 键值对, 并设置过期时间
-func (rds *RedisClient) Set(key string, value any, expiraction time.Duration) bool {
-	if err := rds.Client.Set(rds.Context, key, value, expiraction).Err(); err != nil {
+func (rds *Client) Set(key string, value any, expiration time.Duration) bool {
+	if err := rds.Client.Set(rds.Context, key, value, expiration).Err(); err != nil {
 		logger.ErrorString("Redis", "Set", err.Error())
 		return false
 	}
@@ -33,7 +35,7 @@ func (rds *RedisClient) Set(key string, value any, expiraction time.Duration) bo
 }
 
 // Get 获取 Redis 键值对
-func (rds *RedisClient) Get(key string) string {
+func (rds *Client) Get(key string) string {
 	val, err := rds.Client.Get(rds.Context, key).Result()
 	if err != nil {
 		logger.ErrorString("Redis", "Get", err.Error())
@@ -43,10 +45,10 @@ func (rds *RedisClient) Get(key string) string {
 }
 
 // Has 检查 Redis 中是否存在某个键
-func (rds *RedisClient) Has(key string) bool {
+func (rds *Client) Has(key string) bool {
 	_, err := rds.Client.Get(rds.Context, key).Result()
 	if err != nil {
-		if err == redis.Nil {
+		if errors.Is(err, redis.Nil) {
 			logger.ErrorString("Redis", "Has", err.Error())
 		}
 		return false
@@ -55,7 +57,7 @@ func (rds *RedisClient) Has(key string) bool {
 }
 
 // Del 删除 Redis 中的键, 支持多个 key 传参
-func (rds *RedisClient) Del(keys ...string) bool {
+func (rds *Client) Del(keys ...string) bool {
 	if err := rds.Client.Del(rds.Context, keys...).Err(); err != nil {
 		logger.ErrorString("Redis", "Del", err.Error())
 		return false
@@ -64,7 +66,7 @@ func (rds *RedisClient) Del(keys ...string) bool {
 }
 
 // FlushDb 清空 Redis 数据库
-func (rds *RedisClient) FlushDb() bool {
+func (rds *Client) FlushDb() bool {
 	if err := rds.Client.FlushDB(rds.Context).Err(); err != nil {
 		logger.ErrorString("Redis", "FlushDb", err.Error())
 		return false
@@ -74,7 +76,7 @@ func (rds *RedisClient) FlushDb() bool {
 
 // Increment 当参数只有 1 个时,增加 Redis 中的键值对增加 1
 // 当参数有 2 个时,第一个参数为 Key,第二个参数为增加的值 int64 类型
-func (rds *RedisClient) Increment(args ...any) bool {
+func (rds *Client) Increment(args ...any) bool {
 	switch len(args) {
 	case 1:
 		key := args[0].(string)
@@ -98,7 +100,7 @@ func (rds *RedisClient) Increment(args ...any) bool {
 
 // Decrement 当参数只有 1 个时,减少 Redis 中的键值对减少 1
 // 当参数有 2 个时,第一个参数为 Key,第二个参数为减少的值 int64 类型
-func (rds *RedisClient) Decrement(args ...any) bool {
+func (rds *Client) Decrement(args ...any) bool {
 	switch len(args) {
 	case 1:
 		key := args[0].(string)
@@ -151,7 +153,7 @@ func (rds *RedisClient) Decrement(args ...any) bool {
 var once sync.Once
 
 // Redis 全局 Redis, 使用 db 1
-var Redis *RedisClient
+var Redis *Client
 
 // ConnectRedis 连接 redis 数据库,设置全局 Redis 对象
 func ConnectRedis(address, username, password string, db int) {
@@ -160,9 +162,9 @@ func ConnectRedis(address, username, password string, db int) {
 	})
 }
 
-func NewClient(address, username, password string, db int) *RedisClient {
+func NewClient(address, username, password string, db int) *Client {
 
-	rds := &RedisClient{}
+	rds := &Client{}
 	rds.Context = context.Background()
 
 	// 使用 redis 库里的 NewClient 初始化连接

+ 1 - 1
pkg/verifycode/store_reids.go

@@ -12,7 +12,7 @@ import (
 // 验证码存储在 Redis 中
 // 键名格式为:keyPrefix:code
 type RedisStore struct {
-	RedisClient *redis.RedisClient
+	RedisClient *redis.Client
 	KeyPrefix   string
 }
 

+ 7 - 0
routes/api.go

@@ -69,9 +69,16 @@ func RegisterAPIRoutes(router *gin.Engine) {
 		tpc := new(controllers.TopicsController)
 		tpcGroup := v1.Group("/topics")
 		{
+			tpcGroup.GET("", tpc.Index)
 			tpcGroup.POST("", middlewares.AuthJWT(), tpc.Store)
 			tpcGroup.PUT("/:id", middlewares.AuthJWT(), tpc.Update)
 			tpcGroup.DELETE("/:id", middlewares.AuthJWT(), tpc.Delete)
+			tpcGroup.GET("/:id", tpc.Show)
+		}
+		lsc := new(controllers.LinksController)
+		linksGroup := v1.Group("/links")
+		{
+			linksGroup.GET("", lsc.Index)
 		}
 	}
 }