初始化项目框架,完成部分接口开发
This commit is contained in:
17
utility/ecode/common.go
Normal file
17
utility/ecode/common.go
Normal file
@ -0,0 +1,17 @@
|
||||
package ecode
|
||||
|
||||
var (
|
||||
OK = New(0, "success")
|
||||
Sub = New(1, "") // 自定义错误信息
|
||||
Fail = New(2, "server_error")
|
||||
InvalidOperation = New(3, "invalid_operation")
|
||||
Params = New(4, "params_error")
|
||||
Logout = New(5, "not_login")
|
||||
Disabled = New(6, "account_disabled")
|
||||
Denied = New(7, "permission_denied")
|
||||
Expire = New(8, "token_expired")
|
||||
Auth = New(1000, "auth_failed")
|
||||
Password = New(1001, "password_incorrect")
|
||||
EmailExist = New(1002, "email_exists")
|
||||
NotFound = New(1003, "not_found")
|
||||
)
|
||||
90
utility/ecode/ecode.go
Normal file
90
utility/ecode/ecode.go
Normal file
@ -0,0 +1,90 @@
|
||||
package ecode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"server/utility/i18n"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
code int
|
||||
message string
|
||||
sub string
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
func New(code int, message string) Error {
|
||||
return Error{
|
||||
code: code,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func (e Error) Params(params ...interface{}) Error {
|
||||
e.params = append(e.params, params...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.Message()
|
||||
}
|
||||
|
||||
func (e Error) Sub(sub string) Error {
|
||||
e.sub = sub
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Error) Message() string {
|
||||
if e.message != "" && len(e.params) > 0 {
|
||||
e.message = fmt.Sprintf(e.message, e.params...)
|
||||
}
|
||||
if e.sub != "" {
|
||||
if e.message != "" {
|
||||
if len(e.params) > 0 {
|
||||
e.message = fmt.Sprintf(e.message, e.params...)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", e.message, e.sub)
|
||||
}
|
||||
return e.sub
|
||||
}
|
||||
return e.message
|
||||
}
|
||||
|
||||
// MessageI18n 返回国际化消息
|
||||
func (e Error) MessageI18n(ctx context.Context) string {
|
||||
// 如果有子消息,优先使用子消息的国际化
|
||||
if e.sub != "" {
|
||||
return i18n.T(ctx, e.sub)
|
||||
}
|
||||
|
||||
// 否则使用主消息的国际化
|
||||
if e.message != "" {
|
||||
// 尝试从国际化系统获取消息
|
||||
i18nMsg := i18n.T(ctx, e.message)
|
||||
if i18nMsg != e.message {
|
||||
// 如果找到了国际化消息,使用它
|
||||
if len(e.params) > 0 {
|
||||
return fmt.Sprintf(i18nMsg, e.params...)
|
||||
}
|
||||
return i18nMsg
|
||||
}
|
||||
|
||||
// 如果没有找到国际化消息,使用原来的逻辑
|
||||
if len(e.params) > 0 {
|
||||
return fmt.Sprintf(e.message, e.params...)
|
||||
}
|
||||
return e.message
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e Error) Code() gcode.Code {
|
||||
return gcode.New(e.code, e.Message(), "customer")
|
||||
}
|
||||
|
||||
func (e Error) Detail() interface{} {
|
||||
return "customer"
|
||||
}
|
||||
44
utility/encrypt/password.go
Normal file
44
utility/encrypt/password.go
Normal file
@ -0,0 +1,44 @@
|
||||
package encrypt
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
// EncryptPassword 使用 bcrypt 算法对明文密码进行加密。
|
||||
//
|
||||
// 参数:
|
||||
// - password: 明文密码字符串。
|
||||
//
|
||||
// 返回值:
|
||||
// - 加密后的密码哈希(string)。
|
||||
// - 可能出现的错误(error)。
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// hashed, err := EncryptPassword("mySecret123")
|
||||
// if err != nil {
|
||||
// // 处理错误
|
||||
// }
|
||||
func EncryptPassword(password string) (string, error) {
|
||||
// 使用 bcrypt 的默认成本因子(10)进行加密
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
// ComparePassword 比较明文密码与加密后的密码哈希是否匹配。
|
||||
//
|
||||
// 参数:
|
||||
// - hashedPassword: 已加密的密码哈希。
|
||||
// - password: 用户输入的明文密码。
|
||||
//
|
||||
// 返回值:
|
||||
// - 如果匹配返回 true,否则返回 false。
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// match := ComparePassword(storedHash, "userInput")
|
||||
func ComparePassword(hashedPassword, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
275
utility/i18n/i18n.go
Normal file
275
utility/i18n/i18n.go
Normal file
@ -0,0 +1,275 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// 支持的语言列表
|
||||
var SupportedLanguages = []string{"zh-CN", "en-US"}
|
||||
|
||||
// 默认语言
|
||||
const DefaultLanguage = "en-US"
|
||||
|
||||
// 语言映射表
|
||||
var languageMap = map[string]map[string]string{
|
||||
"zh-CN": {
|
||||
"hello": "你好,世界",
|
||||
// 通用消息
|
||||
"success": "操作成功",
|
||||
"server_error": "服务器内部错误",
|
||||
"invalid_operation": "非法的操作请求",
|
||||
"params_error": "请求参数错误",
|
||||
"not_login": "用户未登录",
|
||||
"account_disabled": "账户已被禁用",
|
||||
"permission_denied": "没有权限执行该操作",
|
||||
"token_expired": "token已过期",
|
||||
"not_found": "资源不存在",
|
||||
"forbidden": "禁止访问",
|
||||
"unauthorized": "未授权访问",
|
||||
"unknown_error": "未知错误",
|
||||
|
||||
// 用户相关
|
||||
"auth_failed": "账户名或密码不正确",
|
||||
"password_incorrect": "密码不正确",
|
||||
"email_exists": "该邮箱已被注册",
|
||||
"password_mismatch": "两次密码输入不一致",
|
||||
"database_query_failed": "数据库查询失败",
|
||||
"data_conversion_failed": "数据转换失败",
|
||||
"token_generation_failed": "Token 生成失败",
|
||||
"password_encryption_failed": "密码加密失败",
|
||||
"registration_failed": "注册失败",
|
||||
"user_not_found": "用户不存在或已被禁用",
|
||||
"email_not_found": "未找到该邮箱注册账户",
|
||||
|
||||
// 管理员相关
|
||||
"admin_not_found": "管理员不存在",
|
||||
"admin_query_failed": "查询管理员信息失败",
|
||||
"invalid_token_format": "无效的token格式",
|
||||
"password_update_failed": "密码更新失败",
|
||||
"token_parse_failed": "Token解析失败",
|
||||
"invalid_token": "无效的Token",
|
||||
|
||||
// 小说相关
|
||||
"book_query_failed": "小说查询失败",
|
||||
"book_exists": "小说已存在",
|
||||
"book_create_failed": "小说创建失败",
|
||||
"book_not_found": "小说不存在",
|
||||
"book_update_failed": "小说更新失败",
|
||||
"book_delete_failed": "小说删除失败",
|
||||
"chapter_not_found": "章节不存在",
|
||||
"insufficient_points": "积分不足",
|
||||
"chapter_locked": "章节已锁定,需要积分解锁",
|
||||
|
||||
// 分类相关
|
||||
"category_query_failed": "分类查询失败",
|
||||
"category_exists": "分类已存在",
|
||||
"category_create_failed": "分类创建失败",
|
||||
"category_not_found": "分类不存在",
|
||||
"category_update_failed": "分类更新失败",
|
||||
"category_delete_failed": "分类删除失败",
|
||||
"category_type_invalid": "分类类型无效,只能为1(男频)或2(女频)",
|
||||
|
||||
// 标签相关
|
||||
"tag_query_failed": "标签查询失败",
|
||||
"tag_exists": "标签已存在",
|
||||
"tag_create_failed": "标签创建失败",
|
||||
"tag_not_found": "标签不存在",
|
||||
"tag_update_failed": "标签更新失败",
|
||||
"tag_delete_failed": "标签删除失败",
|
||||
// 章节相关
|
||||
"chapter_query_failed": "章节查询失败",
|
||||
"chapter_create_failed": "章节创建失败",
|
||||
"chapter_update_failed": "章节更新失败",
|
||||
"chapter_delete_failed": "章节删除失败",
|
||||
// 反馈相关
|
||||
"feedback_create_failed": "反馈提交失败",
|
||||
// 阅读记录相关
|
||||
"read_record_create_failed": "阅读记录创建失败",
|
||||
"read_record_query_failed": "阅读记录查询失败",
|
||||
"read_record_not_found": "阅读记录不存在",
|
||||
"read_record_delete_failed": "阅读记录删除失败",
|
||||
// 关注作者相关
|
||||
"user_follow_author_query_failed": "关注作者查询失败",
|
||||
"user_follow_author_exists": "已关注该作者",
|
||||
"user_follow_author_create_failed": "关注作者失败",
|
||||
"user_follow_author_not_found": "关注记录不存在",
|
||||
"user_follow_author_delete_failed": "取消关注失败",
|
||||
},
|
||||
"en-US": {
|
||||
"hello": "Hello World!",
|
||||
// Common messages
|
||||
"success": "Operation successful",
|
||||
"server_error": "Internal server error",
|
||||
"invalid_operation": "Invalid operation request",
|
||||
"params_error": "Request parameter error",
|
||||
"not_login": "User not logged in",
|
||||
"account_disabled": "Account has been disabled",
|
||||
"permission_denied": "No permission to perform this operation",
|
||||
"token_expired": "Token has expired",
|
||||
"not_found": "Resource not found",
|
||||
"forbidden": "Access forbidden",
|
||||
"unauthorized": "Unauthorized access",
|
||||
"unknown_error": "Unknown error",
|
||||
|
||||
// User related
|
||||
"auth_failed": "Incorrect username or password",
|
||||
"password_incorrect": "Incorrect password",
|
||||
"email_exists": "This email has already been registered",
|
||||
"password_mismatch": "Passwords do not match",
|
||||
"database_query_failed": "Database query failed",
|
||||
"data_conversion_failed": "Data conversion failed",
|
||||
"token_generation_failed": "Token generation failed",
|
||||
"password_encryption_failed": "Password encryption failed",
|
||||
"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",
|
||||
|
||||
// Admin related
|
||||
"admin_not_found": "Administrator not found",
|
||||
"admin_query_failed": "Failed to query administrator information",
|
||||
"invalid_token_format": "Invalid token format",
|
||||
"password_update_failed": "Password update failed",
|
||||
"token_parse_failed": "Token parsing failed",
|
||||
"invalid_token": "Invalid token",
|
||||
|
||||
// Novel related
|
||||
"book_query_failed": "Book query failed",
|
||||
"book_exists": "Book already exists",
|
||||
"book_create_failed": "Book creation failed",
|
||||
"book_not_found": "Book not found",
|
||||
"book_update_failed": "Book update failed",
|
||||
"book_delete_failed": "Book deletion failed",
|
||||
"chapter_not_found": "Chapter not found",
|
||||
"insufficient_points": "Insufficient points",
|
||||
"chapter_locked": "Chapter is locked, requires points to unlock",
|
||||
|
||||
// Category related
|
||||
"category_query_failed": "Category query failed",
|
||||
"category_exists": "Category already exists",
|
||||
"category_create_failed": "Category creation failed",
|
||||
"category_not_found": "Category not found",
|
||||
"category_update_failed": "Category update failed",
|
||||
"category_delete_failed": "Category deletion failed",
|
||||
"category_type_invalid": "Invalid category type, must be 1 (male) or 2 (female)",
|
||||
|
||||
// Tag related
|
||||
"tag_query_failed": "Tag query failed",
|
||||
"tag_exists": "Tag already exists",
|
||||
"tag_create_failed": "Tag creation failed",
|
||||
"tag_not_found": "Tag not found",
|
||||
"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",
|
||||
// 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
|
||||
"user_follow_author_query_failed": "User follow author query failed",
|
||||
"user_follow_author_exists": "Already followed this author",
|
||||
"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",
|
||||
},
|
||||
}
|
||||
|
||||
// GetLanguage 从请求头或查询参数获取语言设置
|
||||
func GetLanguage(ctx context.Context) string {
|
||||
// 优先从请求头获取
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
// 从 Accept-Language 头获取
|
||||
acceptLang := r.GetHeader("Accept-Language")
|
||||
if acceptLang != "" {
|
||||
lang := parseAcceptLanguage(acceptLang)
|
||||
if isSupportedLanguage(lang) {
|
||||
return lang
|
||||
}
|
||||
}
|
||||
|
||||
// 从查询参数获取
|
||||
lang := r.Get("lang").String()
|
||||
if isSupportedLanguage(lang) {
|
||||
return lang
|
||||
}
|
||||
|
||||
// 从请求头获取自定义语言头
|
||||
lang = r.GetHeader("X-Language")
|
||||
if isSupportedLanguage(lang) {
|
||||
return lang
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultLanguage
|
||||
}
|
||||
|
||||
// T 翻译消息
|
||||
func T(ctx context.Context, key string) string {
|
||||
lang := GetLanguage(ctx)
|
||||
if messages, exists := languageMap[lang]; exists {
|
||||
if message, exists := messages[key]; exists {
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前语言没有找到,尝试默认语言
|
||||
if lang != DefaultLanguage {
|
||||
if messages, exists := languageMap[DefaultLanguage]; exists {
|
||||
if message, exists := messages[key]; exists {
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都没有找到,返回key本身
|
||||
return key
|
||||
}
|
||||
|
||||
// Tf 翻译消息并格式化
|
||||
func Tf(ctx context.Context, key string, args ...interface{}) string {
|
||||
message := T(ctx, key)
|
||||
if len(args) > 0 {
|
||||
message = fmt.Sprintf(message, args...)
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
// isSupportedLanguage 检查是否为支持的语言
|
||||
func isSupportedLanguage(lang string) bool {
|
||||
for _, supported := range SupportedLanguages {
|
||||
if supported == lang {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseAcceptLanguage 解析Accept-Language头
|
||||
func parseAcceptLanguage(acceptLang string) string {
|
||||
// 简单的解析,取第一个语言代码
|
||||
parts := strings.Split(acceptLang, ",")
|
||||
if len(parts) > 0 {
|
||||
lang := strings.TrimSpace(parts[0])
|
||||
// 移除质量值
|
||||
if idx := strings.Index(lang, ";"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
return lang
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetSupportedLanguages 获取支持的语言列表
|
||||
func GetSupportedLanguages() []string {
|
||||
return SupportedLanguages
|
||||
}
|
||||
78
utility/jwt/jwt.go
Normal file
78
utility/jwt/jwt.go
Normal file
@ -0,0 +1,78 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"server/utility/ecode"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
secretKey = []byte("1a40c1d5b7a1b0f0fasdfaf835e0b24ca292a6")
|
||||
issuer = "novel"
|
||||
)
|
||||
|
||||
type (
|
||||
TokenIn struct {
|
||||
UserId int64 // 用户 ID
|
||||
Role string // 权限标识
|
||||
}
|
||||
TokenOut struct {
|
||||
UserId int64 // 用户 ID
|
||||
Role string // 权限标识
|
||||
JTI string // JWT 唯一标识
|
||||
}
|
||||
|
||||
jwtClaims struct {
|
||||
UserId int64 `json:"user_id"` // 用户 ID
|
||||
Role string `json:"Role"` // 权限标识
|
||||
JTI string `json:"jti"` // 唯一标识
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
)
|
||||
|
||||
func GenerateToken(in *TokenIn) (string, error) {
|
||||
claims := jwtClaims{
|
||||
UserId: in.UserId,
|
||||
Role: in.Role,
|
||||
JTI: uuid.NewString(),
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: issuer,
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(secretKey)
|
||||
}
|
||||
|
||||
func ParseToken(tokenString string) (*TokenOut, error) {
|
||||
if strings.HasPrefix(tokenString, "Bearer ") {
|
||||
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return secretKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return nil, ecode.Expire.Sub("token_expired")
|
||||
}
|
||||
return nil, ecode.Fail.Sub("token_parse_failed")
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*jwtClaims)
|
||||
if !ok || !token.Valid {
|
||||
return nil, ecode.InvalidOperation.Sub("invalid_token")
|
||||
}
|
||||
|
||||
return &TokenOut{
|
||||
UserId: claims.UserId,
|
||||
Role: claims.Role,
|
||||
JTI: claims.JTI,
|
||||
}, nil
|
||||
}
|
||||
106
utility/myCasbin/casbin.go
Normal file
106
utility/myCasbin/casbin.go
Normal file
@ -0,0 +1,106 @@
|
||||
package myCasbin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
)
|
||||
|
||||
type myCasbin struct {
|
||||
*casbin.Enforcer
|
||||
}
|
||||
|
||||
var (
|
||||
instance *myCasbin
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
ctx := context.Background()
|
||||
once.Do(func() {
|
||||
modelPath := g.Config().MustGet(ctx, "casbin.modelPath").String()
|
||||
enforcer, err := casbin.NewEnforcer(modelPath, adapter.NewAdapter(
|
||||
adapter.Options{
|
||||
GDB: g.DB(),
|
||||
FieldName: &adapter.FieldName{PType: "p_type"},
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "init casbin error: %v", err)
|
||||
}
|
||||
|
||||
enforcer.LoadPolicy()
|
||||
|
||||
enforcer.AddGroupingPolicy(consts.UserRoleCode, consts.GuestRoleCode)
|
||||
enforcer.AddGroupingPolicy(consts.AuthorRoleCode, consts.UserRoleCode)
|
||||
enforcer.AddGroupingPolicy(consts.AdminRoleCode, consts.AuthorRoleCode)
|
||||
// guest
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// user
|
||||
{
|
||||
// book
|
||||
// chapter
|
||||
// feedback
|
||||
// user
|
||||
enforcer.AddPolicy("admin", "/user/info", "GET", "获取用户信息")
|
||||
}
|
||||
// author
|
||||
{
|
||||
// book
|
||||
enforcer.AddPolicy("admin", "/book", "GET", "获取图书列表")
|
||||
// chapter
|
||||
|
||||
// category
|
||||
enforcer.AddPolicy("admin", "/category", "GET", "获取分类列表")
|
||||
}
|
||||
|
||||
// admin
|
||||
{
|
||||
|
||||
// feedback
|
||||
enforcer.AddPolicy("admin", "/feedback", "GET", "获取反馈列表")
|
||||
// category
|
||||
enforcer.AddPolicy("admin", "/category", "POST", "创建分类")
|
||||
enforcer.AddPolicy("admin", "/category", "PUT", "更新分类")
|
||||
enforcer.AddPolicy("admin", "/category", "DELETE", "删除分类")
|
||||
// admin
|
||||
enforcer.AddPolicy("admin", "/admin/info", "GET", "获取管理员用户信息")
|
||||
}
|
||||
instance = &myCasbin{Enforcer: enforcer}
|
||||
|
||||
})
|
||||
glog.Infof(ctx, "init casbin success")
|
||||
}
|
||||
|
||||
func GetMyCasbin() *myCasbin {
|
||||
if instance == nil {
|
||||
panic("casbin not init")
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
// HasPermission 判断给定的权限标识是否拥有访问指定 URL 和方法的权限。
|
||||
//
|
||||
// 参数:
|
||||
// - permission: 权限标识(如角色名或用户 ID)
|
||||
// - url: 请求的路径(如 "/api/user/list")
|
||||
// - method: HTTP 请求方法(如 "GET", "POST")
|
||||
//
|
||||
// 返回:
|
||||
// - access: 如果有权限则为 true;否则为 false。
|
||||
// - 若校验过程中发生错误,将记录日志并返回 false。
|
||||
func (m *myCasbin) HasPermission(permission, url, method string) (access bool) {
|
||||
enforce, err := m.Enforcer.Enforce(permission, url, method)
|
||||
if err != nil {
|
||||
glog.Errorf(context.Background(), "enforce error: %v", err)
|
||||
return
|
||||
}
|
||||
return enforce
|
||||
}
|
||||
99
utility/oss/aliyun/aliyun.go
Normal file
99
utility/oss/aliyun/aliyun.go
Normal file
@ -0,0 +1,99 @@
|
||||
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
|
||||
}
|
||||
26
utility/oss/oss.go
Normal file
26
utility/oss/oss.go
Normal file
@ -0,0 +1,26 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// registry 存储各个平台的实现
|
||||
var clients = make(map[string]OssClient)
|
||||
|
||||
// Register 用于注册平台实现
|
||||
func Register(name string, client OssClient) {
|
||||
clients[name] = client
|
||||
}
|
||||
|
||||
// GetClient 获取指定平台的实现
|
||||
func GetClient(name string) (OssClient, bool) {
|
||||
client, ok := clients[name]
|
||||
return client, ok
|
||||
}
|
||||
Reference in New Issue
Block a user