🚧 improve image encryption and decryption
This commit is contained in:
@@ -45,6 +45,12 @@ type (
|
||||
Avatar string `json:"avatar"`
|
||||
Status int64 `json:"status"`
|
||||
}
|
||||
AdminLoginRequest {
|
||||
Account string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
Dots string `json:"dots"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
)
|
||||
|
||||
// OAuth请求参数
|
||||
@@ -95,6 +101,11 @@ type (
|
||||
ThumbX int64 `json:"thumb_x"`
|
||||
ThumbY int64 `json:"thumb_y"`
|
||||
}
|
||||
TextCaptchaResponse {
|
||||
Key string `json:"key"`
|
||||
Image string `json:"image"`
|
||||
Thumb string `json:"thumb"`
|
||||
}
|
||||
)
|
||||
|
||||
// 用户服务
|
||||
@@ -128,6 +139,10 @@ service auth {
|
||||
// 获取微信公众号二维码
|
||||
@handler getWechatOffiaccountQrcode
|
||||
post /wechat/offiaccount/qrcode (OAuthWechatRequest) returns (string)
|
||||
|
||||
// 管理员登录
|
||||
@handler adminLogin
|
||||
post /admin/login (AdminLoginRequest) returns (LoginResponse)
|
||||
}
|
||||
|
||||
@server (
|
||||
@@ -250,6 +265,10 @@ service auth {
|
||||
|
||||
@handler generateSlideBasicCaptcha
|
||||
get /slide/generate returns (SlideCaptchaResponse)
|
||||
|
||||
// 文字点选验证码
|
||||
@handler generateTextCaptcha
|
||||
get /text/generate returns (TextCaptchaResponse)
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -731,12 +750,23 @@ type (
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
Password string `json:"password"`
|
||||
Dots string `json:"dots"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
SinglePrivateImageRequest {
|
||||
ID int64 `json:"id"`
|
||||
Password string `json:"password"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
CoordinateMeta {
|
||||
ID int64 `json:"id"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
ImageCount int64 `json:"image_count"`
|
||||
Country string `json:"country"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
}
|
||||
CoordinateListResponse {
|
||||
Records []CoordinateMeta `json:"records"`
|
||||
@@ -884,6 +914,10 @@ service auth {
|
||||
@handler getPrivateImageList
|
||||
post /image/private/list (PrivateImageListRequest) returns (AllImageListResponse)
|
||||
|
||||
// 获取解密单个隐私加密图片
|
||||
@handler getPrivateImageUrl
|
||||
post /image/private/url/single (SinglePrivateImageRequest) returns (string)
|
||||
|
||||
// 获取图像经纬度列表
|
||||
@handler getCoordinateList
|
||||
post /coordinate/list returns (CoordinateListResponse)
|
||||
@@ -1037,3 +1071,37 @@ service auth {
|
||||
post /logout returns (string)
|
||||
}
|
||||
|
||||
type (
|
||||
UserMeta {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Status int64 `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
UserInfoListResponse {
|
||||
Records []UserMeta `json:"records"`
|
||||
}
|
||||
)
|
||||
|
||||
// 系统服务
|
||||
@server (
|
||||
group: system // 微服务分组
|
||||
prefix: /api/auth/system // 微服务前缀
|
||||
timeout: 10s // 超时时间
|
||||
maxBytes: 10485760 // 最大请求大小
|
||||
signature: true // 是否开启签名验证
|
||||
middleware: SecurityHeadersMiddleware,CasbinVerifyMiddleware,NonceMiddleware,AuthMiddleware // 注册中间件
|
||||
MaxConns: true // 是否开启最大连接数限制
|
||||
Recover: true // 是否开启自动恢复
|
||||
jwt: Auth // 是否开启jwt验证
|
||||
)
|
||||
service auth {
|
||||
// 获取用户列表
|
||||
@handler getUserList
|
||||
post /user/list returns (UserInfoListResponse)
|
||||
}
|
||||
|
||||
|
@@ -90,6 +90,8 @@ Signature:
|
||||
Encrypt:
|
||||
# 密钥(32)
|
||||
Key: p3380puliiep184buh8d5dvujeerqtem
|
||||
PublicKey: api/etc/common_rsa_public_key.pem
|
||||
PrivateKey: api/etc/common_rsa_private_key.pem
|
||||
# Redis 配置
|
||||
Redis:
|
||||
# Redis 地址
|
||||
|
27
app/auth/api/etc/common_rsa_private_key.pem
Normal file
27
app/auth/api/etc/common_rsa_private_key.pem
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAseOyY9ETmhkaSfUj9LqvU9/+Jo81a+pKGgKGBPB0If+YkCH0
|
||||
Frw1mCtZoCsidZeEMXtSaS1U/lbmKs2L6vExcZr8uSAGb2NXuQn+y8wPnEjAvsU7
|
||||
KaP/cUK11R/6JRHQes5izliNQfdUdnlSuN0X9PFoozuaELb0+SUTTLKkd1mnDk7x
|
||||
3bMN6CNNsTg/zT5hYeuGkunyr5QA4YpofnBC8YID6YlD/mw7x8S4JRPFCmq58Hgq
|
||||
+C2eW716PibyMbCftWMVASvy/OgxnRwvJVho/Br14YiDl3qQWiUPxUYcE1mUrW5t
|
||||
qjARKc0OX5Owid+/ydwtxg8hi/yTskUfjUnXtQIDAQABAoIBACD3MkrfJwPKnR2R
|
||||
iT1ED1O60c1xgpPiEiNpzk5CBTN7u1kSgbpo3IG7nttYwwUJtBy7XtVQ6kxL7FGI
|
||||
T+KVGfWUpDrmXWrs/Qe0e3xm74mlzdpMkJ8x3heuJiY9y8xs1ba8YoEc1eigng1q
|
||||
hFLv3g2tYxfE5tMsJI+7OC1heasIKd88ZO170/mLpDzaKFI0+tUlRCyOIfP8ltCE
|
||||
Bm+1jDeVeWI3HTWESbEX8+eCA/av/h0/JBmtvqZpK2ZYLQMBv9t0ekVqSmm6T9Mj
|
||||
cy/S9Lb7qXoW/2m4QFkD9ph9RY5HJWNZcEKwJNs3ecJJiMD52aGA9obqH9GH5SBj
|
||||
yDq42eECgYEAxTlYngXh3cQ81/KcZxJE9ZoFnmrKeqMX7g4OUyTzLDhUk7K+oGZI
|
||||
wn+K31GpDgC5fpjLsvpKVsnjOjluTLcnRsJ0OUyITHNQo4Z/FzgxwNLnsM99+h3E
|
||||
/75/3xqwnoCWuRSvZHKD6Zk/uBu1UV8rsCJRwUYvzIAVwZxd0Aabjm0CgYEA5udG
|
||||
ILV20HTZq3auHj1x16idhB8Nemg1GyD+hNxzVLZ8Zkrj3HjjukJv1FOOOPkKhmtl
|
||||
3nrYX/8gwxh7HEC+coi7WQqbbvlLIiKaAlYJB7DKip02n3dlzUmp9kpB5EcjJuvX
|
||||
hDrN/s/i6q9M3XWP2zl0oLwkv/GJzs/3cZ7mAWkCgYALcpSuN3Ewyh8t+asSYIEY
|
||||
MGR7GX+/NpBBBRfXw6FJw8tE928RKF64y2ZoJ/lEEs6xhnTsYpLGDtndm0/HrCnf
|
||||
dZIBcWvH5DmeBESEOILKynMgVCrfxbKVlZ0eehIeYSBehdDYZ704ZejI6vLPUlLa
|
||||
2mMccNJ9cEHTBxx64qdM0QKBgBELpauoebrtxVvZCQWGd6757ZbhS/drVfBIwUFB
|
||||
nOn2Brzubl/KNNV9LhA4ktk12UcPCpgf7XU4ukxstDnjtaty2JG8LLlGgftlHoVp
|
||||
oIUG0gzlijC/ea5r77YUyUR20+t9oY1LYgWbhx7YDg6TLSl71lY/TV82D3xK8fNb
|
||||
TZNxAoGAZQlW9+UMOtptfzOh4HLeEs9y2/KvlfUYClgcKyz8FuGeuOMyf8bgFK8L
|
||||
OzGNkEFOgU1CQ9j6de3Id996qK1sGiZwyBVCEXqnnLLUk3yUGbO32wVXniJSrrlT
|
||||
0bekyTJEjnol2E4Rv3WrX7ddGunU2R7p35HkXoWBXHGfwd7F3eI=
|
||||
-----END RSA PRIVATE KEY-----
|
9
app/auth/api/etc/common_rsa_public_key.pem
Normal file
9
app/auth/api/etc/common_rsa_public_key.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAseOyY9ETmhkaSfUj9Lqv
|
||||
U9/+Jo81a+pKGgKGBPB0If+YkCH0Frw1mCtZoCsidZeEMXtSaS1U/lbmKs2L6vEx
|
||||
cZr8uSAGb2NXuQn+y8wPnEjAvsU7KaP/cUK11R/6JRHQes5izliNQfdUdnlSuN0X
|
||||
9PFoozuaELb0+SUTTLKkd1mnDk7x3bMN6CNNsTg/zT5hYeuGkunyr5QA4YpofnBC
|
||||
8YID6YlD/mw7x8S4JRPFCmq58Hgq+C2eW716PibyMbCftWMVASvy/OgxnRwvJVho
|
||||
/Br14YiDl3qQWiUPxUYcE1mUrW5tqjARKc0OX5Owid+/ydwtxg8hi/yTskUfjUnX
|
||||
tQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
@@ -15,7 +15,9 @@ type Config struct {
|
||||
AccessSecret string
|
||||
}
|
||||
Encrypt struct {
|
||||
Key string
|
||||
Key string
|
||||
PublicKey string
|
||||
PrivateKey string
|
||||
}
|
||||
Mysql struct {
|
||||
DataSource string
|
||||
|
@@ -0,0 +1,21 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/captcha"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func GenerateTextCaptchaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := captcha.NewGenerateTextCaptchaLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GenerateTextCaptcha()
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@ import (
|
||||
share "schisandra-album-cloud-microservices/app/auth/api/internal/handler/share"
|
||||
sms "schisandra-album-cloud-microservices/app/auth/api/internal/handler/sms"
|
||||
storage "schisandra-album-cloud-microservices/app/auth/api/internal/handler/storage"
|
||||
system "schisandra-album-cloud-microservices/app/auth/api/internal/handler/system"
|
||||
token "schisandra-album-cloud-microservices/app/auth/api/internal/handler/token"
|
||||
user "schisandra-album-cloud-microservices/app/auth/api/internal/handler/user"
|
||||
websocket "schisandra-album-cloud-microservices/app/auth/api/internal/handler/websocket"
|
||||
@@ -61,6 +62,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/slide/generate",
|
||||
Handler: captcha.GenerateSlideBasicCaptchaHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/text/generate",
|
||||
Handler: captcha.GenerateTextCaptchaHandler(serverCtx),
|
||||
},
|
||||
}...,
|
||||
),
|
||||
rest.WithPrefix("/api/captcha"),
|
||||
@@ -395,6 +401,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/image/private/list",
|
||||
Handler: storage.GetPrivateImageListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/private/url/single",
|
||||
Handler: storage.GetPrivateImageUrlHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/recent/list",
|
||||
@@ -453,6 +464,24 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
rest.WithMaxBytes(104857600),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
rest.WithMiddlewares(
|
||||
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.CasbinVerifyMiddleware, serverCtx.NonceMiddleware, serverCtx.AuthMiddleware},
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/list",
|
||||
Handler: system.GetUserListHandler(serverCtx),
|
||||
},
|
||||
}...,
|
||||
),
|
||||
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
|
||||
rest.WithSignature(serverCtx.Config.Signature),
|
||||
rest.WithPrefix("/api/auth/system"),
|
||||
rest.WithTimeout(10000*time.Millisecond),
|
||||
rest.WithMaxBytes(10485760),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
rest.WithMiddlewares(
|
||||
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.CasbinVerifyMiddleware, serverCtx.NonceMiddleware},
|
||||
@@ -474,6 +503,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
rest.WithMiddlewares(
|
||||
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware},
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/admin/login",
|
||||
Handler: user.AdminLoginHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/login",
|
||||
|
@@ -0,0 +1,29 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func GetPrivateImageUrlHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.SinglePrivateImageRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := storage.NewGetPrivateImageUrlLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetPrivateImageUrl(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/system"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func GetUserListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := system.NewGetUserListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetUserList()
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
29
app/auth/api/internal/handler/user/admin_login_handler.go
Normal file
29
app/auth/api/internal/handler/user/admin_login_handler.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/user"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func AdminLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.AdminLoginRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := user.NewAdminLoginLogic(r.Context(), svcCtx)
|
||||
resp, err := l.AdminLogin(r, &req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"schisandra-album-cloud-microservices/common/captcha/generate"
|
||||
"schisandra-album-cloud-microservices/common/errors"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GenerateTextCaptchaLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGenerateTextCaptchaLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateTextCaptchaLogic {
|
||||
return &GenerateTextCaptchaLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GenerateTextCaptchaLogic) GenerateTextCaptcha() (resp *types.TextCaptchaResponse, err error) {
|
||||
captcha, err := generate.GenerateBasicTextCaptcha(l.svcCtx.TextCaptcha, l.svcCtx.RedisClient, l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.New(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return &types.TextCaptchaResponse{
|
||||
Key: captcha["key"].(string),
|
||||
Image: captcha["image"].(string),
|
||||
Thumb: captcha["thumb"].(string),
|
||||
}, nil
|
||||
}
|
@@ -49,8 +49,8 @@ func (l *DeleteShareRecordLogic) DeleteShareRecord(req *types.DeleteShareRecordR
|
||||
return "", errors.New("delete share record failed")
|
||||
}
|
||||
shareVisit := tx.ScaStorageShareVisit
|
||||
shareVisitDeleted, err := shareVisit.Where(shareVisit.ShareID.Eq(req.ID), shareVisit.UserID.Eq(uid)).Delete()
|
||||
if err != nil || shareVisitDeleted.RowsAffected == 0 {
|
||||
_, err = shareVisit.Where(shareVisit.ShareID.Eq(req.ID), shareVisit.UserID.Eq(uid)).Delete()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return "", errors.New("delete share visit record failed")
|
||||
}
|
||||
|
@@ -54,23 +54,25 @@ func (l *QueryShareImageLogic) QueryShareImage(req *types.QueryShareImageRequest
|
||||
shareData, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, errors.New("share code not found")
|
||||
return nil, errors.New("没有找到分享记录")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var storageShare model.ScaStorageShare
|
||||
if err := json.Unmarshal([]byte(shareData), &storageShare); err != nil {
|
||||
return nil, errors.New("unmarshal share data failed")
|
||||
return nil, errors.New("解析分享记录失败")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if storageShare.AccessPassword != "" && storageShare.AccessPassword != req.AccessPassword {
|
||||
return nil, errors.New("incorrect password")
|
||||
return nil, errors.New("密码错误")
|
||||
}
|
||||
|
||||
// 检查分享是否过期
|
||||
if storageShare.ExpireTime.Before(time.Now()) {
|
||||
return nil, errors.New("share link has expired")
|
||||
if storageShare.ValidityPeriod > 0 {
|
||||
if storageShare.ExpireTime.Before(time.Now()) {
|
||||
return nil, errors.New("分享已过期")
|
||||
}
|
||||
}
|
||||
|
||||
// 检查访问限制
|
||||
|
@@ -87,7 +87,10 @@ func (l *UploadShareImageLogic) UploadShareImage(req *types.ShareImageRequest) (
|
||||
if err != nil {
|
||||
return "", errors.New("invalid expire date")
|
||||
}
|
||||
expiryTime := l.GenerateExpiryTime(time.Now(), duration)
|
||||
var expiryTime time.Time
|
||||
if duration > 0 {
|
||||
expiryTime = l.GenerateExpiryTime(time.Now(), duration)
|
||||
}
|
||||
storageShare := model.ScaStorageShare{
|
||||
UserID: uid,
|
||||
AlbumID: album.ID,
|
||||
|
@@ -35,13 +35,18 @@ func (l *GetCoordinateListLogic) GetCoordinateList() (resp *types.CoordinateList
|
||||
storageLocation.ID,
|
||||
storageLocation.Longitude,
|
||||
storageLocation.Latitude,
|
||||
storageLocation.Country,
|
||||
storageLocation.Province,
|
||||
storageLocation.City,
|
||||
storageInfo.ID.Count().As("image_count"),
|
||||
).Join(
|
||||
storageInfo,
|
||||
storageLocation.ID.EqCol(storageInfo.LocationID),
|
||||
).Where(storageLocation.UserID.Eq(uid),
|
||||
storageInfo.UserID.Eq(uid),
|
||||
).Scan(&records)
|
||||
).
|
||||
Group(storageLocation.ID).
|
||||
Scan(&records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -1,24 +1,20 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"gorm.io/gen"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"gorm.io/gorm"
|
||||
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
|
||||
"schisandra-album-cloud-microservices/common/captcha/verify"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"schisandra-album-cloud-microservices/common/utils"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -31,9 +27,8 @@ import (
|
||||
|
||||
type GetPrivateImageListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
RestyClient *resty.Client
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetPrivateImageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPrivateImageListLogic {
|
||||
@@ -41,16 +36,6 @@ func NewGetPrivateImageListLogic(ctx context.Context, svcCtx *svc.ServiceContext
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
RestyClient: resty.New().
|
||||
SetTimeout(30 * time.Second). // 总超时时间
|
||||
SetRetryCount(3). // 重试次数
|
||||
SetRetryWaitTime(5 * time.Second). // 重试等待时间
|
||||
SetRetryMaxWaitTime(30 * time.Second). // 最大重试等待
|
||||
AddRetryCondition(func(r *resty.Response, err error) bool {
|
||||
return r.StatusCode() == http.StatusTooManyRequests ||
|
||||
err != nil ||
|
||||
r.StatusCode() >= 500
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +44,29 @@ func (l *GetPrivateImageListLogic) GetPrivateImageList(req *types.PrivateImageLi
|
||||
if !ok {
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
captcha := verify.VerifyBasicTextCaptcha(req.Dots, req.Key, l.svcCtx.RedisClient, l.ctx)
|
||||
if !captcha {
|
||||
return nil, errors.New("验证错误")
|
||||
}
|
||||
if req.Password == "" {
|
||||
return nil, errors.New("密码不能为空")
|
||||
}
|
||||
authUser := l.svcCtx.DB.ScaAuthUser
|
||||
userInfo, err := authUser.
|
||||
Select(authUser.UID, authUser.Password).
|
||||
Where(authUser.UID.Eq(uid)).First()
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if userInfo == nil {
|
||||
return nil, errors.New("密码错误")
|
||||
}
|
||||
if !utils.Verify(userInfo.Password, req.Password) {
|
||||
return nil, errors.New("密码错误")
|
||||
}
|
||||
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
storageThumb := l.svcCtx.DB.ScaStorageThumb
|
||||
conditions := []gen.Condition{
|
||||
storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
@@ -74,10 +80,16 @@ func (l *GetPrivateImageListLogic) GetPrivateImageList(req *types.PrivateImageLi
|
||||
storageInfo.ID,
|
||||
storageInfo.FileName,
|
||||
storageInfo.CreatedAt,
|
||||
storageInfo.Path).
|
||||
storageThumb.ThumbPath,
|
||||
storageInfo.Path,
|
||||
storageThumb.ThumbW,
|
||||
storageThumb.ThumbH,
|
||||
storageThumb.ThumbSize,
|
||||
).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
Where(conditions...).
|
||||
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if len(storageInfoList) == 0 {
|
||||
@@ -110,48 +122,15 @@ func (l *GetPrivateImageListLogic) GetPrivateImageList(req *types.PrivateImageLi
|
||||
g.Go(func() error {
|
||||
defer sem.Release(1)
|
||||
|
||||
// 生成单条缓存键(包含文件唯一标识)
|
||||
imageCacheKey := fmt.Sprintf("%s%s:%s:%s:%s:%v",
|
||||
constant.ImageCachePrefix,
|
||||
uid,
|
||||
"list",
|
||||
req.Provider,
|
||||
req.Bucket,
|
||||
dbFileInfo.ID)
|
||||
// 尝试获取单条缓存
|
||||
if cached, err := l.svcCtx.RedisClient.Get(l.ctx, imageCacheKey).Result(); err == nil {
|
||||
var meta types.ImageMeta
|
||||
if err := json.Unmarshal([]byte(cached), &meta); err == nil {
|
||||
parse, err := time.Parse("2006-01-02 15:04:05", meta.CreatedAt)
|
||||
if err == nil {
|
||||
logx.Error("Parse Time Error:", err)
|
||||
return nil
|
||||
}
|
||||
date := parse.Format("2006年1月2日 星期") + WeekdayMap[parse.Weekday()]
|
||||
value, _ := groupedImages.LoadOrStore(date, []types.ImageMeta{})
|
||||
images := value.([]types.ImageMeta)
|
||||
images = append(images, meta)
|
||||
groupedImages.Store(date, images)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
|
||||
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
|
||||
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Minute*30)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return err
|
||||
}
|
||||
imageBytes, err := l.DownloadAndDecrypt(l.ctx, url, uid)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return err
|
||||
}
|
||||
imageData, err := l.svcCtx.XCipher.Decrypt(imageBytes, []byte(uid))
|
||||
|
||||
thumbnailUrl, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.ThumbPath, time.Minute*30)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 使用 Load 或 Store 确保原子操作
|
||||
value, _ := groupedImages.LoadOrStore(date, []types.ImageMeta{})
|
||||
images := value.([]types.ImageMeta)
|
||||
@@ -159,7 +138,7 @@ func (l *GetPrivateImageListLogic) GetPrivateImageList(req *types.PrivateImageLi
|
||||
images = append(images, types.ImageMeta{
|
||||
ID: dbFileInfo.ID,
|
||||
FileName: dbFileInfo.FileName,
|
||||
URL: base64.StdEncoding.EncodeToString(imageData),
|
||||
Thumbnail: thumbnailUrl,
|
||||
Width: dbFileInfo.ThumbW,
|
||||
Height: dbFileInfo.ThumbH,
|
||||
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
@@ -167,14 +146,6 @@ func (l *GetPrivateImageListLogic) GetPrivateImageList(req *types.PrivateImageLi
|
||||
|
||||
// 重新存储更新后的图像列表
|
||||
groupedImages.Store(date, images)
|
||||
|
||||
// 缓存单条数据(24小时基础缓存 + 随机防雪崩)
|
||||
if data, err := json.Marshal(images); err == nil {
|
||||
expire := 24*time.Hour + time.Duration(rand.Intn(3600))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, imageCacheKey, data, expire).Err(); err != nil {
|
||||
logx.Error("Failed to cache image meta:", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -263,27 +234,3 @@ func (l *GetPrivateImageListLogic) getOssConfigFromCacheOrDb(cacheKey, uid, prov
|
||||
|
||||
return ossConfig, nil
|
||||
}
|
||||
|
||||
func (l *GetPrivateImageListLogic) DownloadAndDecrypt(ctx context.Context, url string, uid string) ([]byte, error) {
|
||||
resp, err := l.RestyClient.R().
|
||||
SetContext(ctx).
|
||||
SetDoNotParseResponse(true). // 保持原始响应流
|
||||
Get(url)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.RawBody().Close()
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode(), resp.Status())
|
||||
}
|
||||
|
||||
// 使用缓冲区分块读取
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buf, resp.RawBody()); err != nil {
|
||||
return nil, fmt.Errorf("read response body failed: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
@@ -0,0 +1,218 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
"schisandra-album-cloud-microservices/common/hybrid_encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"schisandra-album-cloud-microservices/common/utils"
|
||||
"time"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetPrivateImageUrlLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
RestyClient *resty.Client
|
||||
}
|
||||
|
||||
func NewGetPrivateImageUrlLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPrivateImageUrlLogic {
|
||||
return &GetPrivateImageUrlLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
RestyClient: resty.New().
|
||||
SetTimeout(30 * time.Second). // 总超时时间
|
||||
SetRetryCount(3). // 重试次数
|
||||
SetRetryWaitTime(5 * time.Second). // 重试等待时间
|
||||
SetRetryMaxWaitTime(30 * time.Second). // 最大重试等待
|
||||
AddRetryCondition(func(r *resty.Response, err error) bool {
|
||||
return r.StatusCode() == http.StatusTooManyRequests ||
|
||||
err != nil ||
|
||||
r.StatusCode() >= 500
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// 修改函数签名和实现
|
||||
func (l *GetPrivateImageUrlLogic) GetPrivateImageUrl(req *types.SinglePrivateImageRequest) (string, error) {
|
||||
uid, ok := l.ctx.Value("user_id").(string)
|
||||
if !ok {
|
||||
return "", errors.New("user_id not found")
|
||||
}
|
||||
|
||||
// 构建缓存key
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%v", constant.ImageCachePrefix, uid, "encrypted", req.ID)
|
||||
|
||||
// 检查缓存
|
||||
if cachedData, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result(); err == nil {
|
||||
return cachedData, nil
|
||||
}
|
||||
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
authUser := l.svcCtx.DB.ScaAuthUser
|
||||
var result struct {
|
||||
ID int64 `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Password string `json:"password"`
|
||||
FileType string `json:"file_type"`
|
||||
}
|
||||
err := storageInfo.
|
||||
Select(
|
||||
storageInfo.ID,
|
||||
storageInfo.Path,
|
||||
storageInfo.FileType,
|
||||
authUser.Password).
|
||||
LeftJoin(authUser, authUser.UID.EqCol(storageInfo.UserID)).
|
||||
Where(storageInfo.ID.Eq(req.ID), storageInfo.UserID.Eq(uid),
|
||||
storageInfo.IsEncrypted.Eq(constant.Encrypt), storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket), authUser.UID.Eq(uid)).
|
||||
Group(storageInfo.ID).Scan(&result)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", err
|
||||
}
|
||||
if result.ID == 0 {
|
||||
return "", errors.New("image not found")
|
||||
}
|
||||
verify := utils.Verify(result.Password, req.Password)
|
||||
if !verify {
|
||||
return "", errors.New("invalid password")
|
||||
}
|
||||
|
||||
// 加载用户oss配置信息
|
||||
cacheOssConfigKey := constant.UserOssConfigPrefix + uid + ":" + req.Provider
|
||||
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheOssConfigKey, uid, req.Provider)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
service, err := l.svcCtx.StorageManager.GetStorage(uid, ossConfig)
|
||||
if err != nil {
|
||||
return "", errors.New("get storage failed")
|
||||
}
|
||||
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, result.Path, time.Minute*15)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return "", errors.New("get private image url failed")
|
||||
}
|
||||
|
||||
resp, err := l.RestyClient.R().
|
||||
SetContext(l.ctx).
|
||||
SetDoNotParseResponse(true). // 保持原始响应流
|
||||
Get(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("download private image failed: %w", err)
|
||||
}
|
||||
defer resp.RawBody().Close()
|
||||
|
||||
body, err := io.ReadAll(resp.RawBody())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
privateKeyPath := filepath.Join(dir, l.svcCtx.Config.Encrypt.PrivateKey)
|
||||
privateKey, err := os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pem, err := hybrid_encrypt.ImportPrivateKeyPEM(privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
image, err := hybrid_encrypt.DecryptImage(pem, body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
base64Str := base64.StdEncoding.EncodeToString(image)
|
||||
|
||||
// 设置缓存,过期时间设为12小时
|
||||
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, base64Str, 12*time.Hour).Err()
|
||||
if err != nil {
|
||||
logx.Errorf("cache private image failed: %v", err)
|
||||
}
|
||||
|
||||
return base64Str, nil
|
||||
}
|
||||
|
||||
// 提取解密操作为函数
|
||||
func (l *GetPrivateImageUrlLogic) decryptConfig(config *model.ScaStorageConfig) (*storageConfig.StorageConfig, error) {
|
||||
accessKey, err := encrypt.Decrypt(config.AccessKey, l.svcCtx.Config.Encrypt.Key)
|
||||
if err != nil {
|
||||
return nil, errors.New("decrypt access key failed")
|
||||
}
|
||||
secretKey, err := encrypt.Decrypt(config.SecretKey, l.svcCtx.Config.Encrypt.Key)
|
||||
if err != nil {
|
||||
return nil, errors.New("decrypt secret key failed")
|
||||
}
|
||||
return &storageConfig.StorageConfig{
|
||||
Provider: config.Provider,
|
||||
Endpoint: config.Endpoint,
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
BucketName: config.Bucket,
|
||||
Region: config.Region,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 从缓存或数据库中获取 OSS 配置
|
||||
func (l *GetPrivateImageUrlLogic) getOssConfigFromCacheOrDb(cacheKey, uid, provider string) (*storageConfig.StorageConfig, error) {
|
||||
result, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return nil, errors.New("get oss config failed")
|
||||
}
|
||||
|
||||
var ossConfig *storageConfig.StorageConfig
|
||||
if result != "" {
|
||||
var redisOssConfig model.ScaStorageConfig
|
||||
if err = json.Unmarshal([]byte(result), &redisOssConfig); err != nil {
|
||||
return nil, errors.New("unmarshal oss config failed")
|
||||
}
|
||||
return l.decryptConfig(&redisOssConfig)
|
||||
}
|
||||
|
||||
// 缓存未命中,从数据库中加载
|
||||
scaOssConfig := l.svcCtx.DB.ScaStorageConfig
|
||||
dbOssConfig, err := scaOssConfig.Where(scaOssConfig.UserID.Eq(uid), scaOssConfig.Provider.Eq(provider)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 缓存数据库配置
|
||||
ossConfig, err = l.decryptConfig(dbOssConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
marshalData, err := json.Marshal(dbOssConfig)
|
||||
if err != nil {
|
||||
return nil, errors.New("marshal oss config failed")
|
||||
}
|
||||
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, marshalData, 0).Err()
|
||||
if err != nil {
|
||||
return nil, errors.New("set oss config failed")
|
||||
}
|
||||
|
||||
return ossConfig, nil
|
||||
}
|
@@ -14,6 +14,7 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
"schisandra-album-cloud-microservices/common/hybrid_encrypt"
|
||||
"schisandra-album-cloud-microservices/common/storage/config"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -50,13 +52,16 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 解析上传配置信息
|
||||
settingResult, err := l.parseUploadSettingResult(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 解析上传的文件
|
||||
file, header, err := l.getUploadedFile(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
@@ -76,12 +81,6 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 解析上传配置信息
|
||||
settingResult, err := l.parseUploadSettingResult(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 使用 `errgroup.Group` 处理并发任务
|
||||
var (
|
||||
faceId int64
|
||||
@@ -110,13 +109,25 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
var imageBytes []byte
|
||||
|
||||
var uploadReader io.Reader = bytes.NewReader(data)
|
||||
if settingResult.Encrypt {
|
||||
encryptedData, err := l.svcCtx.XCipher.Encrypt(data, []byte(uid))
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imageBytes = encryptedData
|
||||
publicKeyPath := filepath.Join(dir, l.svcCtx.Config.Encrypt.PublicKey)
|
||||
publicKey, err := os.ReadFile(publicKeyPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pem, err := hybrid_encrypt.ImportPublicKeyPEM(publicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
image, err := hybrid_encrypt.EncryptImage(pem, data)
|
||||
uploadReader = bytes.NewReader(image)
|
||||
}
|
||||
|
||||
// 上传文件到 OSS
|
||||
@@ -126,7 +137,7 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
}
|
||||
defer sem.Release(1)
|
||||
|
||||
fileUrl, thumbUrl, err := l.uploadFileToOSS(uid, header, bytes.NewReader(imageBytes), thumbnail, result)
|
||||
fileUrl, thumbUrl, err := l.uploadFileToOSS(uid, header, uploadReader, thumbnail, result, settingResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -210,7 +221,7 @@ func (l *UploadFileLogic) parseUploadSettingResult(r *http.Request) (types.Uploa
|
||||
}
|
||||
|
||||
// 上传文件到 OSS
|
||||
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file io.Reader, thumbnail io.Reader, result types.File) (string, string, error) {
|
||||
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file io.Reader, thumbnail io.Reader, result types.File, settingResult types.UploadSetting) (string, string, error) {
|
||||
cacheKey := constant.UserOssConfigPrefix + uid + ":" + result.Provider
|
||||
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheKey, uid, result.Provider)
|
||||
if err != nil {
|
||||
@@ -220,7 +231,6 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
|
||||
if err != nil {
|
||||
return "", "", errors.New("get storage failed")
|
||||
}
|
||||
|
||||
objectKey := path.Join(
|
||||
constant.ImageSpace,
|
||||
uid,
|
||||
@@ -228,6 +238,15 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
|
||||
l.classifyFile(result.FileType, result.IsScreenshot),
|
||||
fmt.Sprintf("%s_%s%s", strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)), kgo.SimpleUuid(), filepath.Ext(header.Filename)),
|
||||
)
|
||||
if settingResult.Encrypt {
|
||||
objectKey = path.Join(
|
||||
constant.ImageSpace,
|
||||
uid,
|
||||
time.Now().Format("2006/01"), // 按年/月划分目录
|
||||
"encrypted",
|
||||
fmt.Sprintf("%s_%s%s", strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)), kgo.SimpleUuid(), ".enc"),
|
||||
)
|
||||
}
|
||||
|
||||
_, err = service.UploadFileSimple(l.ctx, ossConfig.BucketName, objectKey, file, map[string]string{
|
||||
"Content-Type": header.Header.Get("Content-Type"),
|
||||
@@ -241,7 +260,7 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
|
||||
uid,
|
||||
time.Now().Format("2006/01"), // 按年/月划分目录
|
||||
l.classifyFile(result.FileType, result.IsScreenshot),
|
||||
fmt.Sprintf("%s_%s%s", strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)), kgo.SimpleUuid(), filepath.Ext(header.Filename)),
|
||||
fmt.Sprintf("%s_%s", strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)), kgo.SimpleUuid()),
|
||||
)
|
||||
_, err = service.UploadFileSimple(l.ctx, ossConfig.BucketName, thumbObjectKey, thumbnail, map[string]string{
|
||||
"Content-Type": header.Header.Get("Content-Type"),
|
||||
@@ -249,6 +268,7 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
|
||||
if err != nil {
|
||||
return "", "", errors.New("upload thumbnail file failed")
|
||||
}
|
||||
|
||||
return objectKey, thumbObjectKey, nil
|
||||
}
|
||||
|
||||
|
30
app/auth/api/internal/logic/system/get_user_list_logic.go
Normal file
30
app/auth/api/internal/logic/system/get_user_list_logic.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetUserListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserListLogic {
|
||||
return &GetUserListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetUserListLogic) GetUserList() (resp *types.UserInfoListResponse, err error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return
|
||||
}
|
59
app/auth/api/internal/logic/user/admin_login_logic.go
Normal file
59
app/auth/api/internal/logic/user/admin_login_logic.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
"schisandra-album-cloud-microservices/common/captcha/verify"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/errors"
|
||||
"schisandra-album-cloud-microservices/common/i18n"
|
||||
"schisandra-album-cloud-microservices/common/utils"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AdminLoginLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAdminLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminLoginLogic {
|
||||
return &AdminLoginLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AdminLoginLogic) AdminLogin(r *http.Request, req *types.AdminLoginRequest) (resp *types.LoginResponse, err error) {
|
||||
captcha := verify.VerifyBasicTextCaptcha(req.Dots, req.Key, l.svcCtx.RedisClient, l.ctx)
|
||||
if !captcha {
|
||||
return nil, errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "captcha.verificationFailure"))
|
||||
}
|
||||
authUser := l.svcCtx.DB.ScaAuthUser
|
||||
permissionRule := l.svcCtx.DB.ScaAuthPermissionRule
|
||||
adminUser, err := authUser.
|
||||
LeftJoin(permissionRule, authUser.UID.EqCol(permissionRule.V0)).
|
||||
Where(authUser.Username.Eq(req.Account), authUser.Password.Eq(req.Password), permissionRule.V1.Eq(constant.Admin)).
|
||||
Group(authUser.UID).First()
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
if adminUser == nil {
|
||||
return nil, errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.notPermission"))
|
||||
}
|
||||
if !utils.Verify(adminUser.Password, req.Password) {
|
||||
return nil, errors.New(http.StatusInternalServerError, i18n.FormatText(l.ctx, "login.invalidPassword"))
|
||||
}
|
||||
data, err := HandleLoginJWT(adminUser, l.svcCtx, true, r, l.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
@@ -60,7 +60,11 @@ func (l *PhoneLoginLogic) PhoneLogin(r *http.Request, req *types.PhoneLoginReque
|
||||
if userInfo == nil {
|
||||
uid := idgen.NextId()
|
||||
uidStr := strconv.FormatInt(uid, 10)
|
||||
avatar := utils2.GenerateAvatar(uidStr)
|
||||
avatar, err := l.svcCtx.PN.Generate(uidStr, false).ToBase64()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
name := randomname.GenerateName()
|
||||
male := constant2.Male
|
||||
user := &model.ScaAuthUser{
|
||||
|
@@ -12,7 +12,6 @@ import (
|
||||
errors2 "schisandra-album-cloud-microservices/common/errors"
|
||||
"schisandra-album-cloud-microservices/common/i18n"
|
||||
"schisandra-album-cloud-microservices/common/random_name"
|
||||
"schisandra-album-cloud-microservices/common/utils"
|
||||
"strconv"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
@@ -58,7 +57,11 @@ func (l *WechatOffiaccountLoginLogic) WechatOffiaccountLogin(r *http.Request, re
|
||||
// 创建用户
|
||||
uid := idgen.NextId()
|
||||
uidStr := strconv.FormatInt(uid, 10)
|
||||
avatar := utils.GenerateAvatar(uidStr)
|
||||
avatar, err := l.svcCtx.PN.Generate(uidStr, false).ToBase64()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
name := randomname.GenerateName()
|
||||
|
||||
addUser := &model2.ScaAuthUser{
|
||||
|
@@ -3,11 +3,12 @@ package svc
|
||||
import (
|
||||
"github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/landaiqing/go-xcipher"
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/nsqio/go-nsq"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/wenlng/go-captcha/v2/click"
|
||||
"github.com/wenlng/go-captcha/v2/rotate"
|
||||
"github.com/wenlng/go-captcha/v2/slide"
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
@@ -46,13 +47,14 @@ type ServiceContext struct {
|
||||
WechatOfficial *officialAccount.OfficialAccount
|
||||
RotateCaptcha rotate.Captcha
|
||||
SlideCaptcha slide.Captcha
|
||||
TextCaptcha click.Captcha
|
||||
Sensitive *sensitive.Manager
|
||||
StorageManager *manager.Manager
|
||||
MinioClient *minio.Client
|
||||
GeoRegionData *geo_json.RegionData
|
||||
NSQProducer *nsq.Producer
|
||||
ZincClient *zincx.ZincClient
|
||||
XCipher *xcipher.XCipher
|
||||
PN *pixelnebula.PixelNebula
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
@@ -72,6 +74,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
WechatOfficial: wechat_official.NewWechatPublic(c.Wechat.AppID, c.Wechat.AppSecret, c.Wechat.Token, c.Wechat.AESKey, c.Redis.Host, c.Redis.Pass, c.Redis.DB),
|
||||
RotateCaptcha: initialize.NewRotateCaptcha(),
|
||||
SlideCaptcha: initialize.NewSlideCaptcha(),
|
||||
TextCaptcha: initialize.NewTextCaptcha(),
|
||||
Sensitive: sensitivex.NewSensitive(),
|
||||
StorageManager: storage.InitStorageManager(),
|
||||
AiSvcRpc: aiservice.NewAiService(zrpc.MustNewClient(c.AiSvcRpc)),
|
||||
@@ -79,7 +82,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
GeoRegionData: geo_json.NewGeoJSON(),
|
||||
NSQProducer: nsqx.NewNsqProducer(c.NSQ.NSQDHost),
|
||||
ZincClient: zincx.NewZincClient(c.Zinc.URL, c.Zinc.Username, c.Zinc.Password),
|
||||
XCipher: xcipher.NewXCipher([]byte(c.Encrypt.Key)),
|
||||
PN: pixelnebula.NewPixelNebula().WithDefaultCache(),
|
||||
}
|
||||
return serviceContext
|
||||
}
|
||||
|
@@ -18,6 +18,13 @@ type AddImageToAlbumRequest struct {
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
type AdminLoginRequest struct {
|
||||
Account string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
Dots string `json:"dots"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -175,6 +182,9 @@ type CoordinateMeta struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
ImageCount int64 `json:"image_count"`
|
||||
Country string `json:"country"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
}
|
||||
|
||||
type DeleteImageRequest struct {
|
||||
@@ -325,6 +335,8 @@ type PrivateImageListRequest struct {
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
Password string `json:"password"`
|
||||
Dots string `json:"dots"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type QueryDeleteRecordRequest struct {
|
||||
@@ -517,6 +529,13 @@ type SingleImageRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type SinglePrivateImageRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
Password string `json:"password"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
type SlideCaptchaResponse struct {
|
||||
Key string `json:"key"`
|
||||
Image string `json:"image"`
|
||||
@@ -572,6 +591,12 @@ type StroageNode struct {
|
||||
Children []StorageMeta `json:"children"`
|
||||
}
|
||||
|
||||
type TextCaptchaResponse struct {
|
||||
Key string `json:"key"`
|
||||
Image string `json:"image"`
|
||||
Thumb string `json:"thumb"`
|
||||
}
|
||||
|
||||
type ThingDetailListRequest struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Provider string `json:"provider"`
|
||||
@@ -609,6 +634,21 @@ type UploadRequest struct {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
type UserInfoListResponse struct {
|
||||
Records []UserMeta `json:"records"`
|
||||
}
|
||||
|
||||
type UserMeta struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Status int64 `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type UserSecuritySettingResponse struct {
|
||||
BindPhone bool `json:"bind_phone,default=false"`
|
||||
BindEmail bool `json:"bind_email,default=falsel"`
|
||||
@@ -633,3 +673,8 @@ type WechatOffiaccountLoginRequest struct {
|
||||
Openid string `json:"openid"`
|
||||
ClientId string `json:"client_id"`
|
||||
}
|
||||
|
||||
type ImageStreamResponse struct {
|
||||
ContentType string `json:"content_type"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ passwordNotMatch = "password not match!"
|
||||
passwordFormatError = "password format error!"
|
||||
resetPasswordError = "reset password error!"
|
||||
loginSuccess = "login success!"
|
||||
|
||||
notPermission = "not permission!"
|
||||
[sms]
|
||||
smsSendTooFrequently = "sms send too frequently!"
|
||||
smsSendFailed = "sms send failed!"
|
||||
|
@@ -15,6 +15,7 @@ passwordNotMatch = "两次输入的密码不一致!"
|
||||
passwordFormatError = "密码格式错误!"
|
||||
resetPasswordError = "重置密码失败!"
|
||||
loginSuccess = "登录成功!"
|
||||
notPermission = "无权访问!"
|
||||
|
||||
[sms]
|
||||
smsSendTooFrequently = "验证码发送过于频繁,请稍后再试!"
|
||||
|
Reference in New Issue
Block a user