完善功能

This commit is contained in:
2025-07-16 15:16:40 +08:00
parent b2871ec0d2
commit f68a5b360b
123 changed files with 4643 additions and 931 deletions

View File

@ -31,6 +31,7 @@ var languageMap = map[string]map[string]string{
"forbidden": "禁止访问",
"unauthorized": "未授权访问",
"unknown_error": "未知错误",
"user_id_invalid": "用户ID无效请重新登录",
// 用户相关
"auth_failed": "账户名或密码不正确",
@ -44,6 +45,11 @@ var languageMap = map[string]map[string]string{
"registration_failed": "注册失败",
"user_not_found": "用户不存在或已被禁用",
"email_not_found": "未找到该邮箱注册账户",
"user_id_required": "用户ID不能为空",
"user_query_failed": "用户查询失败",
"purchase_query_failed": "购买记录查询失败",
"score_deduction_failed": "积分扣除失败",
"purchase_record_failed": "购买记录创建失败",
// 管理员相关
"admin_not_found": "管理员不存在",
@ -63,6 +69,7 @@ var languageMap = map[string]map[string]string{
"chapter_not_found": "章节不存在",
"insufficient_points": "积分不足",
"chapter_locked": "章节已锁定,需要积分解锁",
"book_id_required": "书籍ID不能为空",
// 分类相关
"category_query_failed": "分类查询失败",
@ -81,10 +88,16 @@ var languageMap = map[string]map[string]string{
"tag_update_failed": "标签更新失败",
"tag_delete_failed": "标签删除失败",
// 章节相关
"chapter_query_failed": "章节查询失败",
"chapter_create_failed": "章节创建失败",
"chapter_update_failed": "章节更新失败",
"chapter_delete_failed": "章节删除失败",
"chapter_query_failed": "章节查询失败",
"chapter_create_failed": "章节创建失败",
"chapter_update_failed": "章节更新失败",
"chapter_delete_failed": "章节删除失败",
"chapter_book_id_required": "书籍ID不能为空",
"chapter_id_required": "章节ID不能为空",
"point_log_failed": "积分日志记录失败",
"purchase_id_failed": "获取购买记录ID失败",
"progress_invalid": "阅读进度无效必须在0-100之间",
"read_record_update_failed": "阅读记录更新失败",
// 反馈相关
"feedback_create_failed": "反馈提交失败",
// 阅读记录相关
@ -98,6 +111,37 @@ var languageMap = map[string]map[string]string{
"user_follow_author_create_failed": "关注作者失败",
"user_follow_author_not_found": "关注记录不存在",
"user_follow_author_delete_failed": "取消关注失败",
// 作者相关
"author_query_failed": "作者查询失败",
"author_user_exists": "该用户已绑定作者",
"author_create_failed": "作者创建失败",
"author_not_found": "作者不存在",
"author_update_failed": "作者更新失败",
"author_delete_failed": "作者删除失败",
// 书架相关
"bookshelve_query_failed": "书架查询失败",
"bookshelve_exists": "该书已在书架中",
"bookshelve_create_failed": "加入书架失败",
"bookshelve_bookids_empty": "请选择要移除的书籍",
"bookshelve_delete_failed": "移除书架失败",
// 评分相关
"rating_invalid": "评分无效必须在1-10分之间",
"rating_query_failed": "评分查询失败",
"rating_update_failed": "评分更新失败",
"rating_create_failed": "评分创建失败",
"rating_calculation_failed": "评分计算失败",
"book_rating_update_failed": "书籍评分更新失败",
// 我的书籍列表相关
"type_invalid": "类型参数无效必须为1-3",
"bookshelf_query_failed": "书架查询失败",
"history_query_failed": "历史记录查询失败",
"history_create_failed": "历史记录创建失败",
"history_delete_failed": "历史记录删除失败",
"history_bookids_empty": "请选择要删除的历史记录",
"history_update_failed": "历史记录更新失败",
"bookshelf_update_failed": "书架更新失败",
"chapter_count_failed": "章节统计失败",
"read_chapter_count_failed": "已读章节统计失败",
},
"en-US": {
"hello": "Hello World!",
@ -114,6 +158,7 @@ var languageMap = map[string]map[string]string{
"forbidden": "Access forbidden",
"unauthorized": "Unauthorized access",
"unknown_error": "Unknown error",
"user_id_invalid": "Invalid user ID, please re-login",
// User related
"auth_failed": "Incorrect username or password",
@ -127,6 +172,11 @@ var languageMap = map[string]map[string]string{
"registration_failed": "Registration failed",
"user_not_found": "User does not exist or has been disabled",
"email_not_found": "No registered account found for this email",
"user_id_required": "User ID cannot be empty",
"user_query_failed": "User query failed",
"purchase_query_failed": "Purchase record query failed",
"score_deduction_failed": "Score deduction failed",
"purchase_record_failed": "Purchase record creation failed",
// Admin related
"admin_not_found": "Administrator not found",
@ -146,6 +196,7 @@ var languageMap = map[string]map[string]string{
"chapter_not_found": "Chapter not found",
"insufficient_points": "Insufficient points",
"chapter_locked": "Chapter is locked, requires points to unlock",
"book_id_required": "Book ID cannot be empty",
// Category related
"category_query_failed": "Category query failed",
@ -164,15 +215,21 @@ var languageMap = map[string]map[string]string{
"tag_update_failed": "Tag update failed",
"tag_delete_failed": "Tag deletion failed",
// Chapter related
"chapter_query_failed": "Chapter query failed",
"chapter_create_failed": "Chapter creation failed",
"chapter_update_failed": "Chapter update failed",
"chapter_delete_failed": "Chapter deletion failed",
"chapter_query_failed": "Chapter query failed",
"chapter_create_failed": "Chapter creation failed",
"chapter_update_failed": "Chapter update failed",
"chapter_delete_failed": "Chapter deletion failed",
"chapter_book_id_required": "Book ID cannot be empty",
"chapter_id_required": "Chapter ID cannot be empty",
"point_log_failed": "Point log creation failed",
"purchase_id_failed": "Failed to get purchase record ID",
"progress_invalid": "Reading progress is invalid, must be between 0 and 100",
"read_record_query_failed": "Read record query failed",
"read_record_update_failed": "Read record update failed",
"read_record_create_failed": "Read record creation failed",
// Feedback related
"feedback_create_failed": "Feedback creation failed",
// ReadRecord related
"read_record_create_failed": "Read record creation failed",
"read_record_query_failed": "Read record query failed",
"read_record_not_found": "Read record not found",
"read_record_delete_failed": "Read record deletion failed",
// UserFollowAuthor related
@ -181,6 +238,37 @@ var languageMap = map[string]map[string]string{
"user_follow_author_create_failed": "User follow author creation failed",
"user_follow_author_not_found": "Follow record not found",
"user_follow_author_delete_failed": "Unfollow failed",
// Author related
"author_query_failed": "Author query failed",
"author_user_exists": "User already has an author profile",
"author_create_failed": "Author creation failed",
"author_not_found": "Author not found",
"author_update_failed": "Author update failed",
"author_delete_failed": "Author deletion failed",
// Bookshelve related
"bookshelve_query_failed": "Bookshelf query failed",
"bookshelve_exists": "Book already in bookshelf",
"bookshelve_create_failed": "Failed to add to bookshelf",
"bookshelve_bookids_empty": "Please select books to remove",
"bookshelve_delete_failed": "Failed to remove from bookshelf",
// 评分相关
"rating_invalid": "Rating is invalid, must be between 1-10",
"rating_query_failed": "Rating query failed",
"rating_update_failed": "Rating update failed",
"rating_create_failed": "Rating creation failed",
"rating_calculation_failed": "Rating calculation failed",
"book_rating_update_failed": "Book rating update failed",
// 我的书籍列表相关
"type_invalid": "Type parameter is invalid, must be 1-3",
"bookshelf_query_failed": "Bookshelf query failed",
"history_query_failed": "History query failed",
"history_create_failed": "History creation failed",
"history_delete_failed": "History deletion failed",
"history_bookids_empty": "Please select history records to delete",
"history_update_failed": "History update failed",
"bookshelf_update_failed": "Bookshelf update failed",
"chapter_count_failed": "Chapter count failed",
"read_chapter_count_failed": "Read chapter count failed",
},
}

View File

@ -0,0 +1,60 @@
package amazonsqs
import (
"context"
"encoding/json"
"server/utility/mqtt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
type AmazonSQSClient struct {
client *sqs.Client
}
func NewAmazonSQSClient() *AmazonSQSClient {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
panic(err)
}
return &AmazonSQSClient{
client: sqs.NewFromConfig(cfg),
}
}
func (c *AmazonSQSClient) getQueueURL(ctx context.Context, queueName string) (string, error) {
out, err := c.client.GetQueueUrl(ctx, &sqs.GetQueueUrlInput{
QueueName: aws.String(queueName),
})
if err != nil {
return "", err
}
return *out.QueueUrl, nil
}
func (c *AmazonSQSClient) Publish(ctx context.Context, topic string, payload interface{}) error {
queueURL, err := c.getQueueURL(ctx, topic)
if err != nil {
return err
}
body, err := json.Marshal(payload)
if err != nil {
return err
}
_, err = c.client.SendMessage(ctx, &sqs.SendMessageInput{
QueueUrl: aws.String(queueURL),
MessageBody: aws.String(string(body)),
})
return err
}
func (c *AmazonSQSClient) Subscribe(topic string, handler func(payload interface{})) error {
// 这里实现 SQS 的消息订阅逻辑
return nil
}
func init() {
mqtt.RegisterClient("amazon_sqs", NewAmazonSQSClient())
}

18
utility/mqtt/mqtt.go Normal file
View File

@ -0,0 +1,18 @@
package mqtt
import "context"
type MQTTClient interface {
Publish(ctx context.Context, topic string, payload interface{}) error
Subscribe(topic string, handler func(payload interface{})) error
}
var clients = make(map[string]MQTTClient)
func RegisterClient(name string, client MQTTClient) {
clients[name] = client
}
func GetMQTTClient(name string) MQTTClient {
return clients[name]
}

View File

@ -2,12 +2,13 @@ package myCasbin
import (
"context"
"server/internal/consts"
"sync"
"github.com/casbin/casbin/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/hailaz/gf-casbin-adapter/v2"
"server/internal/consts"
"sync"
adapter "github.com/hailaz/gf-casbin-adapter/v2"
)
type myCasbin struct {
@ -40,30 +41,56 @@ func init() {
enforcer.AddGroupingPolicy(consts.AdminRoleCode, consts.AuthorRoleCode)
// guest
{
enforcer.AddPolicy("guest", "/book/app/list", "GET", "App获取书籍列表")
enforcer.AddPolicy("guest", "/book/app/detail", "GET", "App获取书籍详情")
enforcer.AddPolicy("guest", "/chapter/app/list", "GET", "App获取章节列表")
enforcer.AddPolicy("guest", "/chapter/app/detail", "GET", "App获取章节详情")
enforcer.AddPolicy("guest", "/category", "GET", "获取分类列表")
}
// user
{
// book
enforcer.AddPolicy("user", "/book/shelf/add", "POST", "加入书架")
enforcer.AddPolicy("user", "/book/shelf/remove", "POST", "移除书架")
enforcer.AddPolicy("user", "/book/app/rate", "POST", "App用户评分")
enforcer.AddPolicy("user", "/book/app/my-books", "GET", "获取我的书籍列表")
// chapter
enforcer.AddPolicy("user", "/chapter/app/purchase", "POST", "App购买章节")
enforcer.AddPolicy("user", "/chapter/app/progress", "POST", "App上传阅读进度")
// feedback
enforcer.AddPolicy("user", "/feedback", "POST", "新增反馈")
// user
enforcer.AddPolicy("admin", "/user/info", "GET", "获取用户信息")
enforcer.AddPolicy("user", "/user/info", "GET", "获取用户信息")
enforcer.AddPolicy("user", "/user/delete", "POST", "删除用户")
enforcer.AddPolicy("user", "/user/logout", "POST", "用户登出")
// author follow/unfollow
enforcer.AddPolicy("user", "/author/follow", "POST", "关注作者")
enforcer.AddPolicy("user", "/author/unfollow", "POST", "取消关注作者")
}
// author
{
// book
enforcer.AddPolicy("admin", "/book", "GET", "获取图书列表")
enforcer.AddPolicy("author", "/book", "GET", "获取图书列表")
enforcer.AddPolicy("author", "/book", "POST", "新增图书")
enforcer.AddPolicy("author", "/book", "PUT", "编辑图书")
enforcer.AddPolicy("author", "/book", "DELETE", "删除图书")
// chapter
// category
enforcer.AddPolicy("admin", "/category", "GET", "获取分类列表")
enforcer.AddPolicy("author", "/chapter", "GET", "获取章节列表")
enforcer.AddPolicy("author", "/chapter", "POST", "创建章节")
enforcer.AddPolicy("author", "/chapter", "PUT", "更新章节")
enforcer.AddPolicy("author", "/chapter", "DELETE", "删除章节")
}
// admin
// admin
{
// book
enforcer.AddPolicy("admin", "/book/set-featured", "POST", "设置书籍精选状态")
enforcer.AddPolicy("admin", "/book/set-recommended", "POST", "设置书籍推荐状态")
// author
enforcer.AddPolicy("admin", "/author", "GET", "获取作者列表")
enforcer.AddPolicy("admin", "/author", "POST", "创建作者")
enforcer.AddPolicy("admin", "/author", "PUT", "更新作者")
enforcer.AddPolicy("admin", "/author", "DELETE", "删除作者")
// feedback
enforcer.AddPolicy("admin", "/feedback", "GET", "获取反馈列表")
// category
@ -72,6 +99,7 @@ func init() {
enforcer.AddPolicy("admin", "/category", "DELETE", "删除分类")
// admin
enforcer.AddPolicy("admin", "/admin/info", "GET", "获取管理员用户信息")
enforcer.AddPolicy("admin", "/admin/editPass", "POST", "管理员修改密码")
}
instance = &myCasbin{Enforcer: enforcer}

View File

@ -1,99 +0,0 @@
package aliyun
import (
"bytes"
"context"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/util/grand"
"server/internal/model"
ioss "server/utility/oss"
)
type aliyunClient struct {
bucketName string
key string
secret string
endpoint string
}
// 初始化并注册
func init() {
ctx := context.Background()
client := &aliyunClient{
endpoint: g.Config().MustGet(ctx, "oss.aliyun.endpoint").String(),
key: g.Config().MustGet(ctx, "oss.aliyun.key").String(),
secret: g.Config().MustGet(ctx, "oss.aliyun.secret").String(),
bucketName: g.Config().MustGet(ctx, "oss.aliyun.bucket").String(),
}
ioss.Register("aliyun", client)
glog.Infof(ctx, "注册阿里云OSS成功")
}
func (a *aliyunClient) client(ctx context.Context, endpoint, key, sercret string) (*oss.Client, error) {
client, err := oss.New(endpoint, key, sercret)
return client, err
}
func (a *aliyunClient) bucket(ctx context.Context, client *oss.Client) (*oss.Bucket, error) {
bucket, err := client.Bucket(a.bucketName)
return bucket, err
}
func (a *aliyunClient) bytes(ctx context.Context, in *model.OssBytesInput) (*model.OssOutput, error) {
client, err := a.client(ctx, a.endpoint, a.key, a.secret)
if err != nil {
return nil, err
}
bucket, err := a.bucket(ctx, client)
if err != nil {
return nil, err
}
if in.Name == "" {
in.Name = grand.Digits(32)
}
err = bucket.PutObject(in.Name, bytes.NewReader(in.Bytes))
if err != nil {
return nil, err
}
return &model.OssOutput{
Url: fmt.Sprintf("https://%s.%s/%s", a.bucketName, a.endpoint, in.Name),
}, nil
}
func (a *aliyunClient) UploadFile(ctx context.Context, in *model.OssUploadFileInput) (out *model.OssOutput, err error) {
f, err := in.File.Open()
if err != nil {
return nil, err
}
defer f.Close()
body := make([]byte, in.File.Size)
_, err = f.Read(body)
if err != nil {
return nil, err
}
return a.bytes(ctx, &model.OssBytesInput{
Name: in.Filename,
Bytes: body,
})
}
func (a *aliyunClient) GetFileURL(ctx context.Context, in *model.OssGetFileInput) (out *model.OssOutput, err error) {
client, err := a.client(ctx, a.endpoint, a.key, a.secret)
if err != nil {
return nil, err
}
bucket, err := a.bucket(ctx, client)
if err != nil {
return nil, err
}
if in.Name == "" {
in.Name = grand.Digits(32)
}
err = bucket.PutObjectFromFile(in.Name, in.FilePath)
if err != nil {
return nil, err
}
return &model.OssOutput{
Url: fmt.Sprintf("https://%s.%s/%s", a.bucketName, a.endpoint, in.Name),
}, nil
}

View File

@ -0,0 +1,114 @@
package amazons3
import (
"bytes"
"server/utility/oss"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gogf/gf/crypto/gmd5"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/grand"
)
type AmazonS3Client struct{}
func (c *AmazonS3Client) initSess() (*session.Session, error) {
key := g.Config().GetString("aws.s3.key")
secret := g.Config().GetString("aws.s3.secret")
region := g.Config().GetString("aws.s3.region")
if len(key) == 0 || len(secret) == 0 {
return nil, gerror.New("aws s3 配置错误")
}
config := &aws.Config{
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(key, secret, ""),
}
sess := session.Must(session.NewSession(config))
return sess, nil
}
func (c *AmazonS3Client) Upload(file interface{}) (string, error) {
uploadFile, ok := file.(*ghttp.UploadFile)
if !ok {
return "", gerror.New("参数类型错误")
}
bucket := g.Config().GetString("aws.s3.bucket")
if len(bucket) == 0 {
glog.Warning("bucket为空:", bucket)
return "", gerror.New("aws s3 配置错误")
}
sess, err := c.initSess()
if err != nil {
return "", err
}
uploader := s3manager.NewUploader(sess)
f, err := uploadFile.Open()
if err != nil {
return "", err
}
defer f.Close()
body := make([]byte, uploadFile.Size)
_, err = f.Read(body)
if err != nil {
return "", err
}
key, err := gmd5.Encrypt(uploadFile.Filename + gtime.Datetime() + grand.Digits(6))
ext := gfile.Ext(uploadFile.Filename)
if err != nil {
return "", err
}
r, err := uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(bucket),
Key: aws.String(key + ext),
Body: bytes.NewReader(body),
})
if err != nil {
return "", err
}
return r.Location, nil
}
func (c *AmazonS3Client) UploadLocalFile(path, name string) (string, error) {
bucket := g.Config().GetString("aws.s3.bucket")
if len(bucket) == 0 {
glog.Warning("bucket为空:", bucket)
return "", gerror.New("aws s3 配置错误")
}
sess, err := c.initSess()
if err != nil {
return "", err
}
uploader := s3manager.NewUploader(sess)
f, err := gfile.Open(path)
if err != nil {
return "", err
}
defer f.Close()
body := make([]byte, gfile.Size(path))
_, err = f.Read(body)
if err != nil {
return "", err
}
r, err := uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(bucket),
Key: aws.String(name),
Body: bytes.NewReader(body),
})
if err != nil {
glog.Debug(err)
return "", err
}
return r.Location, nil
}
func init() {
oss.RegisterClient("amazon_s3", &AmazonS3Client{})
}

View File

@ -1,26 +1,16 @@
package oss
import (
"context"
"server/internal/model"
)
// OssClient 是所有云存储平台要实现的统一接口
type OssClient interface {
UploadFile(ctx context.Context, in *model.OssUploadFileInput) (out *model.OssOutput, err error)
GetFileURL(ctx context.Context, in *model.OssGetFileInput) (out *model.OssOutput, err error)
type OSSClient interface {
Upload(file interface{}) (string, error)
UploadLocalFile(path, name string) (string, error)
}
// registry 存储各个平台的实现
var clients = make(map[string]OssClient)
var clients = make(map[string]OSSClient)
// Register 用于注册平台实现
func Register(name string, client OssClient) {
func RegisterClient(name string, client OSSClient) {
clients[name] = client
}
// GetClient 获取指定平台的实现
func GetClient(name string) (OssClient, bool) {
client, ok := clients[name]
return client, ok
func GetOSSClient(name string) OSSClient {
return clients[name]
}