Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfbb3a9acc | |||
| 19fa8b381c | |||
| b7a9682552 | |||
| f638baec64 | |||
| 1262a8dae4 |
@@ -2,7 +2,7 @@ name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["v*"]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -10,14 +10,12 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
156
web/auth/account.go
Normal file
156
web/auth/account.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
s "platform/web/services"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func authClient(clientId string, clientSecrets ...string) (*m.Client, error) {
|
||||
|
||||
// 获取客户端信息
|
||||
client, err := q.Client.
|
||||
Where(
|
||||
q.Client.ClientID.Eq(clientId),
|
||||
q.Client.Status.Eq(1)).
|
||||
Take()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查客户端密钥
|
||||
if client.Spec == m.ClientSpecWeb || client.Spec == m.ClientSpecAPI {
|
||||
if len(clientSecrets) == 0 {
|
||||
return nil, errors.New("客户端密钥错误")
|
||||
}
|
||||
clientSecret := clientSecrets[0]
|
||||
if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil {
|
||||
return nil, errors.New("客户端密钥错误")
|
||||
}
|
||||
}
|
||||
|
||||
// todo 查询客户端关联权限
|
||||
|
||||
// 组织授权信息(一次性请求)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func authUser(loginType PwdLoginType, username, password string) (user *m.User, err error) {
|
||||
switch loginType {
|
||||
case PwdLoginByPhone:
|
||||
user, err = authUserBySms(q.Q, username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
user = &m.User{
|
||||
Phone: username,
|
||||
Username: u.P(username),
|
||||
Status: m.UserStatusEnabled,
|
||||
}
|
||||
}
|
||||
case PwdLoginByEmail:
|
||||
user, err = authUserByEmail(q.Q, username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case PwdLoginByPassword:
|
||||
user, err = authUserByPassword(q.Q, username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, ErrAuthorizeInvalidRequest
|
||||
}
|
||||
|
||||
// 账户状态
|
||||
if user.Status == m.UserStatusDisabled {
|
||||
return nil, core.NewBizErr("账号已禁用")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func authUserBySms(tx *q.Query, username, code string) (*m.User, error) {
|
||||
// 验证验证码
|
||||
err := s.Verifier.VerifySms(context.Background(), username, code)
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("短信认证失败", err)
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
return tx.User.Where(tx.User.Phone.Eq(username)).Take()
|
||||
}
|
||||
|
||||
func authUserByEmail(tx *q.Query, username, code string) (*m.User, error) {
|
||||
return nil, core.NewServErr("邮箱登录不可用")
|
||||
}
|
||||
|
||||
func authUserByPassword(tx *q.Query, username, password string) (*m.User, error) {
|
||||
user, err := tx.User.
|
||||
Where(tx.User.Phone.Eq(username)).
|
||||
Or(tx.User.Email.Eq(username)).
|
||||
Or(tx.User.Username.Eq(username)).
|
||||
Take()
|
||||
if err != nil {
|
||||
slog.Debug("查找用户失败", "error", err)
|
||||
return nil, core.NewBizErr("用户不存在或密码错误")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if user.Password == nil || *user.Password == "" {
|
||||
slog.Debug("用户未设置密码", "username", username)
|
||||
return nil, core.NewBizErr("用户不存在或密码错误")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)) != nil {
|
||||
slog.Debug("密码验证失败", "username", username)
|
||||
return nil, core.NewBizErr("用户不存在或密码错误")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func authAdmin(loginType PwdLoginType, username, password string) (admin *m.Admin, err error) {
|
||||
switch loginType {
|
||||
case PwdLoginByPhone, PwdLoginByEmail:
|
||||
return nil, core.NewServErr("不支持的登录方式:" + string(loginType))
|
||||
case PwdLoginByPassword:
|
||||
admin, err = authAdminByPassword(q.Q, username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, ErrAuthorizeInvalidRequest
|
||||
}
|
||||
|
||||
// 账户状态
|
||||
if admin.Status == m.AdminStatusDisabled {
|
||||
return nil, core.NewBizErr("账号已禁用")
|
||||
}
|
||||
|
||||
return admin, nil
|
||||
}
|
||||
|
||||
func authAdminByPassword(tx *q.Query, username, password string) (*m.Admin, error) {
|
||||
admin, err := tx.Admin.Where(tx.Admin.Username.Eq(username)).Take()
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("账号不存在或密码错误")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if admin.Password == "" {
|
||||
return nil, core.NewBizErr("账号不存在或密码错误")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(password)) != nil {
|
||||
return nil, core.NewBizErr("账号不存在或密码错误")
|
||||
}
|
||||
|
||||
return admin, nil
|
||||
}
|
||||
@@ -6,10 +6,8 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
"platform/web/globals/orm"
|
||||
m "platform/web/models"
|
||||
@@ -22,67 +20,52 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GrantType string
|
||||
// AuthorizeGet 授权端点
|
||||
func AuthorizeGet(ctx *fiber.Ctx) error {
|
||||
|
||||
const (
|
||||
GrantAuthorizationCode = GrantType("authorization_code") // 授权码模式
|
||||
GrantClientCredentials = GrantType("client_credentials") // 客户端凭证模式
|
||||
GrantRefreshToken = GrantType("refresh_token") // 刷新令牌模式
|
||||
GrantPassword = GrantType("password") // 密码模式(私有扩展)
|
||||
)
|
||||
|
||||
type PasswordGrantType string
|
||||
|
||||
const (
|
||||
GrantPasswordSecret = PasswordGrantType("password") // 账号密码
|
||||
GrantPasswordPhone = PasswordGrantType("phone_code") // 手机验证码
|
||||
GrantPasswordEmail = PasswordGrantType("email_code") // 邮箱验证码
|
||||
)
|
||||
|
||||
type TokenReq struct {
|
||||
GrantType GrantType `json:"grant_type" form:"grant_type"`
|
||||
ClientID string `json:"client_id" form:"client_id"`
|
||||
ClientSecret string `json:"client_secret" form:"client_secret"`
|
||||
Scope string `json:"scope" form:"scope"`
|
||||
GrantCodeData
|
||||
GrantClientData
|
||||
GrantRefreshData
|
||||
GrantPasswordData
|
||||
// 检查请求
|
||||
req := new(AuthorizeGetReq)
|
||||
if err := g.Validator.ParseQuery(ctx, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type GrantCodeData struct {
|
||||
Code string `json:"code" form:"code"`
|
||||
RedirectURI string `json:"redirect_uri" form:"redirect_uri"`
|
||||
CodeVerifier string `json:"code_verifier" form:"code_verifier"`
|
||||
// 检查客户端
|
||||
client, err := authClient(req.ClientID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type GrantClientData struct {
|
||||
if client.RedirectURI == nil || *client.RedirectURI != req.RedirectURI {
|
||||
return errors.New("客户端重定向URI错误")
|
||||
}
|
||||
|
||||
type GrantRefreshData struct {
|
||||
RefreshToken string `json:"refresh_token" form:"refresh_token"`
|
||||
// todo 检查 scope
|
||||
|
||||
// 授权确认页面
|
||||
return nil
|
||||
}
|
||||
|
||||
type GrantPasswordData struct {
|
||||
LoginType PasswordGrantType `json:"login_type" form:"login_type"`
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
Remember bool `json:"remember" form:"remember"`
|
||||
type AuthorizeGetReq struct {
|
||||
ResponseType string `json:"response_type" validate:"eq=code"`
|
||||
ClientID string `json:"client_id" validate:"required"`
|
||||
RedirectURI string `json:"redirect_uri" validate:"required"`
|
||||
Scope string `json:"scope"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
type TokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
func AuthorizePost(ctx *fiber.Ctx) error {
|
||||
|
||||
// todo 解析用户授权的范围
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type TokenErrResp struct {
|
||||
Error string `json:"error"`
|
||||
Description string `json:"error_description,omitempty"`
|
||||
type AuthorizePostReq struct {
|
||||
Accept bool `json:"accept"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
// Token 令牌端点
|
||||
func Token(c *fiber.Ctx) error {
|
||||
now := time.Now()
|
||||
|
||||
@@ -165,6 +148,75 @@ func Token(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
type TokenReq struct {
|
||||
GrantType GrantType `json:"grant_type" form:"grant_type"`
|
||||
ClientID string `json:"client_id" form:"client_id"`
|
||||
ClientSecret string `json:"client_secret" form:"client_secret"`
|
||||
Scope string `json:"scope" form:"scope"`
|
||||
GrantCodeData
|
||||
GrantClientData
|
||||
GrantRefreshData
|
||||
GrantPasswordData
|
||||
}
|
||||
|
||||
type GrantCodeData struct {
|
||||
Code string `json:"code" form:"code"`
|
||||
RedirectURI string `json:"redirect_uri" form:"redirect_uri"`
|
||||
CodeVerifier string `json:"code_verifier" form:"code_verifier"`
|
||||
}
|
||||
|
||||
type GrantClientData struct {
|
||||
}
|
||||
|
||||
type GrantRefreshData struct {
|
||||
RefreshToken string `json:"refresh_token" form:"refresh_token"`
|
||||
}
|
||||
|
||||
type GrantPasswordData struct {
|
||||
LoginType PwdLoginType `json:"login_type" form:"login_type"`
|
||||
LoginPool PwdLoginPool `json:"login_pool" form:"login_pool"`
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
Remember bool `json:"remember" form:"remember"`
|
||||
}
|
||||
|
||||
type GrantType string
|
||||
|
||||
const (
|
||||
GrantAuthorizationCode = GrantType("authorization_code") // 授权码模式
|
||||
GrantClientCredentials = GrantType("client_credentials") // 客户端凭证模式
|
||||
GrantRefreshToken = GrantType("refresh_token") // 刷新令牌模式
|
||||
GrantPassword = GrantType("password") // 密码模式(私有扩展)
|
||||
)
|
||||
|
||||
type PwdLoginType string
|
||||
|
||||
const (
|
||||
PwdLoginByPassword = PwdLoginType("password") // 账号密码
|
||||
PwdLoginByPhone = PwdLoginType("phone_code") // 手机验证码
|
||||
PwdLoginByEmail = PwdLoginType("email_code") // 邮箱验证码
|
||||
)
|
||||
|
||||
type PwdLoginPool string
|
||||
|
||||
const (
|
||||
PwdLoginAsUser = PwdLoginPool("user") // 用户池
|
||||
PwdLoginAsAdmin = PwdLoginPool("admin") // 管理员池
|
||||
)
|
||||
|
||||
type TokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
type TokenErrResp struct {
|
||||
Error string `json:"error"`
|
||||
Description string `json:"error_description,omitempty"`
|
||||
}
|
||||
|
||||
func authAuthorizationCode(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m.Session, error) {
|
||||
|
||||
// 检查 code 获取用户授权信息
|
||||
@@ -226,7 +278,7 @@ func authAuthorizationCode(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.
|
||||
session.RefreshTokenExpires = u.P(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second))
|
||||
}
|
||||
|
||||
err = SaveSession(session)
|
||||
err = SaveSession(q.Q, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -249,7 +301,7 @@ func authClientCredential(c *fiber.Ctx, auth *AuthCtx, _ *TokenReq, now time.Tim
|
||||
}
|
||||
|
||||
// 保存会话
|
||||
err := SaveSession(session)
|
||||
err := SaveSession(q.Q, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -261,71 +313,85 @@ func authPassword(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m
|
||||
ip, _ := orm.ParseInet(c.IP()) // 可空字段,忽略异常
|
||||
ua := u.X(c.Get(fiber.HeaderUserAgent))
|
||||
|
||||
// 分池认证
|
||||
var err error
|
||||
var user *m.User
|
||||
err := q.Q.Transaction(func(tx *q.Query) (err error) {
|
||||
switch req.LoginType {
|
||||
case GrantPasswordPhone:
|
||||
user, err = authUserBySms(tx, req.Username, req.Password)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
var admin *m.Admin
|
||||
|
||||
pool := req.LoginPool
|
||||
if pool == "" {
|
||||
pool = PwdLoginAsUser
|
||||
}
|
||||
if user == nil {
|
||||
switch pool {
|
||||
case PwdLoginAsUser:
|
||||
user, err = authUser(req.LoginType, req.Username, req.Password)
|
||||
if err != nil {
|
||||
if req.LoginType != PwdLoginByPhone || !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 手机号首次登录的自动创建用户
|
||||
user = &m.User{
|
||||
Phone: req.Username,
|
||||
Username: u.P(req.Username),
|
||||
Status: m.UserStatusEnabled,
|
||||
}
|
||||
}
|
||||
case GrantPasswordEmail:
|
||||
user, err = authUserByEmail(tx, req.Username, req.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case GrantPasswordSecret:
|
||||
user, err = authUserByPassword(tx, req.Username, req.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return ErrAuthorizeInvalidRequest
|
||||
}
|
||||
|
||||
// 账户状态
|
||||
if user.Status == m.UserStatusDisabled {
|
||||
slog.Debug("账户状态异常", "username", req.Username, "status", user.Status)
|
||||
return core.NewBizErr("账号无法登录")
|
||||
}
|
||||
|
||||
// 更新用户的登录时间
|
||||
user.LastLogin = u.P(time.Now())
|
||||
user.LastLoginIP = ip
|
||||
user.LastLoginUA = ua
|
||||
if err := tx.User.Save(user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
case PwdLoginAsAdmin:
|
||||
admin, err = authAdmin(req.LoginType, req.Username, req.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新管理员登录时间
|
||||
admin.LastLogin = u.P(time.Now())
|
||||
admin.LastLoginIP = ip
|
||||
admin.LastLoginUA = ua
|
||||
}
|
||||
|
||||
// 生成会话
|
||||
session := &m.Session{
|
||||
IP: ip,
|
||||
UA: ua,
|
||||
UserID: &user.ID,
|
||||
ClientID: &auth.Client.ID,
|
||||
Scopes: u.X(req.Scope),
|
||||
AccessToken: uuid.NewString(),
|
||||
AccessTokenExpires: now.Add(time.Duration(env.SessionAccessExpire) * time.Second),
|
||||
}
|
||||
if user != nil {
|
||||
session.UserID = &user.ID
|
||||
}
|
||||
if admin != nil {
|
||||
session.AdminID = &admin.ID
|
||||
}
|
||||
if req.Remember {
|
||||
session.RefreshToken = u.P(uuid.NewString())
|
||||
session.RefreshTokenExpires = u.P(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second))
|
||||
}
|
||||
|
||||
err = SaveSession(session)
|
||||
// 保存用户更新和会话
|
||||
err = q.Q.Transaction(func(tx *q.Query) error {
|
||||
if err := SaveSession(tx, session); err != nil {
|
||||
return err
|
||||
}
|
||||
if user != nil {
|
||||
if err := tx.User.Save(user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if admin != nil {
|
||||
if err := tx.Admin.Save(admin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -353,7 +419,7 @@ func authRefreshToken(_ *fiber.Ctx, _ *AuthCtx, req *TokenReq, now time.Time) (*
|
||||
}
|
||||
|
||||
// 保存令牌
|
||||
err = SaveSession(session)
|
||||
err = SaveSession(q.Q, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -394,14 +460,119 @@ func sendError(c *fiber.Ctx, err error, description ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func Revoke() error {
|
||||
// Revoke 令牌撤销端点
|
||||
func Revoke(ctx *fiber.Ctx) error {
|
||||
_, err := GetAuthCtx(ctx).PermitUser()
|
||||
if err != nil {
|
||||
// 用户未登录
|
||||
return nil
|
||||
}
|
||||
|
||||
func Introspect() error {
|
||||
// 解析请求参数
|
||||
req := new(RevokeReq)
|
||||
if err := ctx.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除会话
|
||||
err = RemoveSession(ctx.Context(), req.AccessToken, req.RefreshToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RevokeReq struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// Introspect 令牌检查端点
|
||||
func Introspect(ctx *fiber.Ctx) error {
|
||||
authCtx := GetAuthCtx(ctx)
|
||||
|
||||
// 尝试验证用户权限
|
||||
if _, err := authCtx.PermitUser(); err == nil {
|
||||
return introspectUser(ctx, authCtx)
|
||||
}
|
||||
|
||||
// 尝试验证管理员权限
|
||||
if _, err := authCtx.PermitAdmin(); err == nil {
|
||||
return introspectAdmin(ctx, authCtx)
|
||||
}
|
||||
|
||||
return ErrAuthenticateForbidden
|
||||
}
|
||||
|
||||
// introspectUser 获取并返回用户信息
|
||||
func introspectUser(ctx *fiber.Ctx, authCtx *AuthCtx) error {
|
||||
// 获取用户信息
|
||||
profile, err := q.User.
|
||||
Where(q.User.ID.Eq(authCtx.User.ID)).
|
||||
Omit(q.User.DeletedAt).
|
||||
Take()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查用户是否设置了密码
|
||||
hasPassword := false
|
||||
if profile.Password != nil && *profile.Password != "" {
|
||||
hasPassword = true
|
||||
profile.Password = nil // 不返回密码
|
||||
}
|
||||
|
||||
// 掩码敏感信息
|
||||
if profile.Phone != "" {
|
||||
profile.Phone = maskPhone(profile.Phone)
|
||||
}
|
||||
if profile.IDNo != nil && *profile.IDNo != "" {
|
||||
profile.IDNo = u.P(maskIdNo(*profile.IDNo))
|
||||
}
|
||||
|
||||
return ctx.JSON(struct {
|
||||
m.User
|
||||
HasPassword bool `json:"has_password"` // 是否设置了密码
|
||||
}{*profile, hasPassword})
|
||||
}
|
||||
|
||||
// introspectAdmin 获取并返回管理员信息
|
||||
func introspectAdmin(ctx *fiber.Ctx, authCtx *AuthCtx) error {
|
||||
// 获取管理员信息
|
||||
profile, err := q.Admin.
|
||||
Where(q.Admin.ID.Eq(authCtx.Admin.ID)).
|
||||
Omit(q.Admin.DeletedAt).
|
||||
Take()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 不返回密码
|
||||
profile.Password = ""
|
||||
|
||||
// 掩码敏感信息
|
||||
if profile.Phone != nil && *profile.Phone != "" {
|
||||
profile.Phone = u.P(maskPhone(*profile.Phone))
|
||||
}
|
||||
|
||||
return ctx.JSON(profile)
|
||||
}
|
||||
|
||||
func maskPhone(phone string) string {
|
||||
if len(phone) < 11 {
|
||||
return phone
|
||||
}
|
||||
return phone[:3] + "****" + phone[7:]
|
||||
}
|
||||
|
||||
func maskIdNo(idNo string) string {
|
||||
if len(idNo) < 18 {
|
||||
return idNo
|
||||
}
|
||||
return idNo[:3] + "*********" + idNo[14:]
|
||||
}
|
||||
|
||||
type CodeContext struct {
|
||||
UserID int32 `json:"user_id"`
|
||||
ClientID int32 `json:"client_id"`
|
||||
@@ -6,15 +6,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"platform/web/core"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
s "platform/web/services"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func Authenticate() fiber.Handler {
|
||||
@@ -123,67 +118,3 @@ func authBasic(_ context.Context, token string) (*AuthCtx, error) {
|
||||
Scopes: []string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authClient(clientId, clientSecret string) (*m.Client, error) {
|
||||
|
||||
// 获取客户端信息
|
||||
client, err := q.Client.
|
||||
Where(
|
||||
q.Client.ClientID.Eq(clientId),
|
||||
q.Client.Status.Eq(1)).
|
||||
Take()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查客户端密钥
|
||||
if client.Spec == m.ClientSpecWeb || client.Spec == m.ClientSpecAPI {
|
||||
if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil {
|
||||
return nil, errors.New("客户端密钥错误")
|
||||
}
|
||||
}
|
||||
|
||||
// todo 查询客户端关联权限
|
||||
|
||||
// 组织授权信息(一次性请求)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func authUserBySms(tx *q.Query, username, code string) (*m.User, error) {
|
||||
// 验证验证码
|
||||
err := s.Verifier.VerifySms(context.Background(), username, code)
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("短信认证失败:%w", err)
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
return tx.User.Where(tx.User.Phone.Eq(username)).Take()
|
||||
}
|
||||
|
||||
func authUserByEmail(tx *q.Query, username, code string) (*m.User, error) {
|
||||
return nil, core.NewServErr("邮箱登录不可用")
|
||||
}
|
||||
|
||||
func authUserByPassword(tx *q.Query, username, password string) (*m.User, error) {
|
||||
user, err := tx.User.
|
||||
Where(tx.User.Phone.Eq(username)).
|
||||
Or(tx.User.Email.Eq(username)).
|
||||
Or(tx.User.Username.Eq(username)).
|
||||
Take()
|
||||
if err != nil {
|
||||
slog.Debug("查找用户失败", "error", err)
|
||||
return nil, core.NewBizErr("用户不存在或密码错误")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if user.Password == nil || *user.Password == "" {
|
||||
slog.Debug("用户未设置密码", "username", username)
|
||||
return nil, core.NewBizErr("用户不存在或密码错误")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)) != nil {
|
||||
slog.Debug("密码验证失败", "username", username)
|
||||
return nil, core.NewBizErr("用户不存在或密码错误")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -29,8 +29,8 @@ func FindSessionByRefresh(refreshToken string, now time.Time) (*m.Session, error
|
||||
).First()
|
||||
}
|
||||
|
||||
func SaveSession(session *m.Session) error {
|
||||
return q.Session.Save(session)
|
||||
func SaveSession(tx *q.Query, session *m.Session) error {
|
||||
return tx.Session.Save(session)
|
||||
}
|
||||
|
||||
func RemoveSession(ctx context.Context, accessToken string, refreshToken string) error {
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"platform/pkg/u"
|
||||
auth2 "platform/web/auth"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// region /revoke
|
||||
|
||||
type RevokeReq struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func Revoke(c *fiber.Ctx) error {
|
||||
_, err := auth2.GetAuthCtx(c).PermitUser()
|
||||
if err != nil {
|
||||
// 用户未登录
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(RevokeReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除会话
|
||||
err = auth2.RemoveSession(c.Context(), req.AccessToken, req.RefreshToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region /profile
|
||||
|
||||
type IntrospectResp struct {
|
||||
m.User
|
||||
HasPassword bool `json:"has_password"` // 是否设置了密码
|
||||
}
|
||||
|
||||
func Introspect(c *fiber.Ctx) error {
|
||||
// 验证权限
|
||||
authCtx, err := auth2.GetAuthCtx(c).PermitUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
profile, err := q.User.
|
||||
Where(q.User.ID.Eq(authCtx.User.ID)).
|
||||
Omit(q.User.DeletedAt).
|
||||
Take()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查用户是否设置了密码
|
||||
hasPassword := false
|
||||
if profile.Password != nil && *profile.Password != "" {
|
||||
hasPassword = true
|
||||
profile.Password = nil // 不返回密码
|
||||
}
|
||||
|
||||
// 掩码敏感信息
|
||||
if profile.Phone != "" {
|
||||
profile.Phone = maskPhone(profile.Phone)
|
||||
}
|
||||
if profile.IDNo != nil && *profile.IDNo != "" {
|
||||
profile.IDNo = u.P(maskIdNo(*profile.IDNo))
|
||||
}
|
||||
return c.JSON(IntrospectResp{*profile, hasPassword})
|
||||
}
|
||||
|
||||
func maskPhone(phone string) string {
|
||||
if len(phone) < 11 {
|
||||
return phone
|
||||
}
|
||||
return phone[:3] + "****" + phone[7:]
|
||||
}
|
||||
|
||||
func maskIdNo(idNo string) string {
|
||||
if len(idNo) < 18 {
|
||||
return idNo
|
||||
}
|
||||
return idNo[:3] + "*********" + idNo[14:]
|
||||
}
|
||||
|
||||
// endregion
|
||||
@@ -55,3 +55,25 @@ type PageResourceBatchReq struct {
|
||||
TimeStart *time.Time `json:"time_start"`
|
||||
TimeEnd *time.Time `json:"time_end"`
|
||||
}
|
||||
|
||||
// PageBatchByAdmin 分页查询所有提取记录
|
||||
func PageBatchByAdmin(c *fiber.Ctx) error {
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(struct{ core.PageReq })
|
||||
if err = g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, total, err := q.LogsUserUsage.FindByPage(req.GetOffset(), req.GetLimit())
|
||||
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,20 +3,40 @@ package handlers
|
||||
import (
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
q "platform/web/queries"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// region ListBill
|
||||
// PageBillByAdmin 分页查询全部账单
|
||||
func PageBillByAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type ListBillReq struct {
|
||||
core.PageReq
|
||||
BillNo *string `json:"bill_no"`
|
||||
Type *int `json:"type"`
|
||||
CreateAfter *time.Time `json:"create_after"`
|
||||
CreateBefore *time.Time `json:"create_before"`
|
||||
// 解析请求参数
|
||||
req := new(core.PageReq)
|
||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
list, total, err := q.Bill.FindByPage(req.GetOffset(), req.GetLimit())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
// ListBill 获取账单列表
|
||||
@@ -79,4 +99,10 @@ func ListBill(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// endregion
|
||||
type ListBillReq struct {
|
||||
core.PageReq
|
||||
BillNo *string `json:"bill_no"`
|
||||
Type *int `json:"type"`
|
||||
CreateAfter *time.Time `json:"create_after"`
|
||||
CreateBefore *time.Time `json:"create_before"`
|
||||
}
|
||||
|
||||
@@ -14,15 +14,36 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// region ListChannels
|
||||
|
||||
type ListChannelsReq struct {
|
||||
core.PageReq
|
||||
AuthType s.ChannelAuthType `json:"auth_type"`
|
||||
ExpireAfter *time.Time `json:"expire_after"`
|
||||
ExpireBefore *time.Time `json:"expire_before"`
|
||||
// PageChannelsByAdmin 分页查询所有通道
|
||||
func PageChannelsByAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(core.PageReq)
|
||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查询通道列表
|
||||
list, total, err := q.Channel.FindByPage(req.GetOffset(), req.GetLimit())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
// 分页查询当前用户通道
|
||||
func ListChannels(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authContext, err := auth.GetAuthCtx(c).PermitUser()
|
||||
@@ -86,28 +107,14 @@ func ListChannels(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region CreateChannel
|
||||
|
||||
type CreateChannelReq struct {
|
||||
ResourceId int32 `json:"resource_id" validate:"required"`
|
||||
AuthType s.ChannelAuthType `json:"auth_type" validate:"required"`
|
||||
Protocol int `json:"protocol" validate:"required"`
|
||||
Count int `json:"count" validate:"required"`
|
||||
Prov *string `json:"prov"`
|
||||
City *string `json:"city"`
|
||||
Isp *int `json:"isp"`
|
||||
}
|
||||
|
||||
type CreateChannelRespItem struct {
|
||||
Proto int `json:"-"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
Username *string `json:"username,omitempty"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
type ListChannelsReq struct {
|
||||
core.PageReq
|
||||
AuthType s.ChannelAuthType `json:"auth_type"`
|
||||
ExpireAfter *time.Time `json:"expire_after"`
|
||||
ExpireBefore *time.Time `json:"expire_before"`
|
||||
}
|
||||
|
||||
// 创建新通道
|
||||
func CreateChannel(c *fiber.Ctx) error {
|
||||
|
||||
// 解析参数
|
||||
@@ -154,16 +161,25 @@ func CreateChannel(c *fiber.Ctx) error {
|
||||
return c.JSON(resp)
|
||||
}
|
||||
|
||||
type CreateChannelResultType string
|
||||
|
||||
// endregion
|
||||
|
||||
// region RemoveChannels
|
||||
|
||||
type RemoveChannelsReq struct {
|
||||
Batch string `json:"batch" validate:"required"`
|
||||
type CreateChannelReq struct {
|
||||
ResourceId int32 `json:"resource_id" validate:"required"`
|
||||
AuthType s.ChannelAuthType `json:"auth_type" validate:"required"`
|
||||
Protocol int `json:"protocol" validate:"required"`
|
||||
Count int `json:"count" validate:"required"`
|
||||
Prov *string `json:"prov"`
|
||||
City *string `json:"city"`
|
||||
Isp *int `json:"isp"`
|
||||
}
|
||||
|
||||
type CreateChannelRespItem struct {
|
||||
Proto int `json:"-"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
Username *string `json:"username,omitempty"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// RemoveChannels 删除通道
|
||||
func RemoveChannels(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitOfficialClient()
|
||||
@@ -186,4 +202,6 @@ func RemoveChannels(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
// endregion
|
||||
type RemoveChannelsReq struct {
|
||||
Batch string `json:"batch" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// ListResourceShort 分页短效套餐
|
||||
func ListResourceShort(c *fiber.Ctx) error {
|
||||
// PageResourceShort 分页查询当前用户短效套餐
|
||||
func PageResourceShort(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||
if err != nil {
|
||||
@@ -24,7 +24,7 @@ func ListResourceShort(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(ListResourceShortReq)
|
||||
req := new(PageResourceShortReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func ListResourceShort(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
type ListResourceShortReq struct {
|
||||
type PageResourceShortReq struct {
|
||||
core.PageReq
|
||||
ResourceNo *string `json:"resource_no"`
|
||||
Active *bool `json:"active"`
|
||||
@@ -97,8 +97,8 @@ type ListResourceShortReq struct {
|
||||
ExpireBefore *time.Time `json:"expire_before"`
|
||||
}
|
||||
|
||||
// ListResourceLong 分页长效套餐
|
||||
func ListResourceLong(c *fiber.Ctx) error {
|
||||
// PageResourceLong 分页查询当前用户长效套餐
|
||||
func PageResourceLong(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||
if err != nil {
|
||||
@@ -106,7 +106,7 @@ func ListResourceLong(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(ListResourceLongReq)
|
||||
req := new(PageResourceLongReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func ListResourceLong(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
type ListResourceLongReq struct {
|
||||
type PageResourceLongReq struct {
|
||||
core.PageReq
|
||||
ResourceNo *string `json:"resource_no"`
|
||||
Active *bool `json:"active"`
|
||||
@@ -179,6 +179,56 @@ type ListResourceLongReq struct {
|
||||
ExpireBefore *time.Time `json:"expire_before"`
|
||||
}
|
||||
|
||||
// PageResourceShortByAdmin 分页查询全部短效套餐
|
||||
func PageResourceShortByAdmin(c *fiber.Ctx) error {
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(struct{ core.PageReq })
|
||||
if err = g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, total, err := q.Resource.
|
||||
LeftJoin(q.ResourceShort, q.ResourceShort.ResourceID.EqCol(q.Resource.ID)).
|
||||
Where(q.Resource.Type.Eq(int(m.ResourceTypeShort))).
|
||||
FindByPage(req.GetOffset(), req.GetLimit())
|
||||
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
// PageResourceLongByAdmin 分页查询全部短效套餐
|
||||
func PageResourceLongByAdmin(c *fiber.Ctx) error {
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(struct{ core.PageReq })
|
||||
if err = g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, total, err := q.Resource.
|
||||
LeftJoin(q.ResourceLong, q.ResourceLong.ResourceID.EqCol(q.Resource.ID)).
|
||||
Where(q.Resource.Type.Eq(int(m.ResourceTypeLong))).
|
||||
FindByPage(req.GetOffset(), req.GetLimit())
|
||||
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
// AllActiveResource 所有可用套餐
|
||||
func AllActiveResource(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
s "platform/web/services"
|
||||
"time"
|
||||
|
||||
@@ -16,18 +17,36 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type TradeCreateReq struct {
|
||||
s.CreateTradeData
|
||||
Type m.TradeType `json:"type" validate:"required"`
|
||||
Resource *s.CreateResourceData `json:"resource,omitempty"`
|
||||
Recharge *s.RechargeProductInfo `json:"recharge,omitempty"`
|
||||
// PageTradeByAdmin 分页查询所有订单
|
||||
func PageTradeByAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type TradeCreateResp struct {
|
||||
PayUrl string `json:"pay_url"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
// 解析请求参数
|
||||
req := new(core.PageReq)
|
||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
list, total, err := q.Trade.FindByPage(req.GetOffset(), req.GetLimit())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(core.PageResp{
|
||||
List: list,
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
func TradeCreate(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||
@@ -67,10 +86,19 @@ func TradeCreate(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
type TradeCompleteReq struct {
|
||||
s.ModifyTradeData
|
||||
type TradeCreateReq struct {
|
||||
s.CreateTradeData
|
||||
Type m.TradeType `json:"type" validate:"required"`
|
||||
Resource *s.CreateResourceData `json:"resource,omitempty"`
|
||||
Recharge *s.RechargeProductInfo `json:"recharge,omitempty"`
|
||||
}
|
||||
|
||||
type TradeCreateResp struct {
|
||||
PayUrl string `json:"pay_url"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
}
|
||||
|
||||
// 完成订单
|
||||
func TradeComplete(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitUser()
|
||||
@@ -93,10 +121,11 @@ func TradeComplete(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
type TradeCancelReq struct {
|
||||
type TradeCompleteReq struct {
|
||||
s.ModifyTradeData
|
||||
}
|
||||
|
||||
// 取消订单
|
||||
func TradeCancel(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitUser()
|
||||
@@ -120,10 +149,11 @@ func TradeCancel(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
type TradeCheckReq struct {
|
||||
type TradeCancelReq struct {
|
||||
s.ModifyTradeData
|
||||
}
|
||||
|
||||
// 检查订单
|
||||
func TradeCheck(c *fiber.Ctx) error {
|
||||
// 解析请求参数
|
||||
req := new(TradeCheckReq)
|
||||
@@ -170,3 +200,7 @@ func TradeCheck(c *fiber.Ctx) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type TradeCheckReq struct {
|
||||
s.ModifyTradeData
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package handlers
|
||||
|
||||
import (
|
||||
"platform/web/auth"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
s "platform/web/services"
|
||||
@@ -10,15 +12,81 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// region /update
|
||||
|
||||
type UpdateUserReq struct {
|
||||
Username string `json:"username" validate:"omitempty,min=3,max=20"`
|
||||
Email string `json:"email" validate:"omitempty,email"`
|
||||
ContactQQ string `json:"contact_qq" validate:"omitempty,qq"`
|
||||
ContactWechat string `json:"contact_wechat" validate:"omitempty,wechat"`
|
||||
// 分页获取用户
|
||||
func PageUserByAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
_, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(core.PageReq)
|
||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查询用户列表
|
||||
users, total, err := q.User.
|
||||
Preload(q.User.Admin).
|
||||
Omit(q.User.Password).
|
||||
FindByPage(req.GetOffset(), req.GetLimit())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if user.Admin != nil {
|
||||
user.Admin = &m.Admin{
|
||||
Name: user.Admin.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(core.PageResp{
|
||||
Total: int(total),
|
||||
Page: req.GetPage(),
|
||||
Size: req.GetSize(),
|
||||
List: users,
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定管理员
|
||||
func BindAdmin(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.GetAuthCtx(c).PermitAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(struct {
|
||||
UserID int `json:"user_id" validate:"required"`
|
||||
})
|
||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
result, err := q.User.Where(
|
||||
q.User.ID.Eq(int32(req.UserID)),
|
||||
q.User.AdminID.IsNull(),
|
||||
).UpdateColumnSimple(
|
||||
q.User.AdminID.Value(authCtx.Admin.ID),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return core.NewBizErr("用户已绑定管理员")
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// 更新用户
|
||||
func UpdateUser(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||
@@ -49,15 +117,14 @@ func UpdateUser(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region /update/account
|
||||
|
||||
type UpdateAccountReq struct {
|
||||
type UpdateUserReq struct {
|
||||
Username string `json:"username" validate:"omitempty,min=3,max=20"`
|
||||
Password string `json:"password" validate:"omitempty,min=6,max=20"`
|
||||
Email string `json:"email" validate:"omitempty,email"`
|
||||
ContactQQ string `json:"contact_qq" validate:"omitempty,qq"`
|
||||
ContactWechat string `json:"contact_wechat" validate:"omitempty,wechat"`
|
||||
}
|
||||
|
||||
// 更新账号信息
|
||||
func UpdateAccount(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||
@@ -86,16 +153,12 @@ func UpdateAccount(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region /update/password
|
||||
|
||||
type UpdatePasswordReq struct {
|
||||
Phone string `json:"phone"`
|
||||
Code string `json:"code"`
|
||||
Password string `json:"password"`
|
||||
type UpdateAccountReq struct {
|
||||
Username string `json:"username" validate:"omitempty,min=3,max=20"`
|
||||
Password string `json:"password" validate:"omitempty,min=6,max=20"`
|
||||
}
|
||||
|
||||
// 更新账号密码
|
||||
func UpdatePassword(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||
@@ -135,4 +198,8 @@ func UpdatePassword(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// endregion
|
||||
type UpdatePasswordReq struct {
|
||||
Phone string `json:"phone"`
|
||||
Code string `json:"code"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/gofiber/contrib/otelfiber/v2"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
"github.com/gofiber/fiber/v2/middleware/requestid"
|
||||
@@ -19,6 +20,14 @@ func ApplyMiddlewares(app *fiber.App) {
|
||||
EnableStackTrace: true,
|
||||
}))
|
||||
|
||||
// cors
|
||||
app.Use(cors.New(cors.Config{
|
||||
AllowCredentials: true,
|
||||
AllowOriginsFunc: func(origin string) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
// logger
|
||||
app.Use(logger.New(logger.Config{
|
||||
Next: func(c *fiber.Ctx) bool {
|
||||
|
||||
@@ -10,12 +10,30 @@ import (
|
||||
|
||||
func ApplyRouters(app *fiber.App) {
|
||||
api := app.Group("/api")
|
||||
userRouter(api)
|
||||
adminRouter(api)
|
||||
|
||||
// 回调
|
||||
callbacks := app.Group("/callback")
|
||||
callbacks.Get("/identify", handlers.IdentifyCallbackNew)
|
||||
|
||||
// 临时
|
||||
if env.RunMode == env.RunModeDev {
|
||||
debug := app.Group("/debug")
|
||||
debug.Get("/sms/:phone", handlers.DebugGetSmsCode)
|
||||
debug.Get("/proxy/register", handlers.DebugRegisterProxyBaiYin)
|
||||
}
|
||||
}
|
||||
|
||||
// 用户接口路由
|
||||
func userRouter(api fiber.Router) {
|
||||
// 认证
|
||||
auth := api.Group("/auth")
|
||||
auth.Get("/authorize", auth2.AuthorizeGet)
|
||||
auth.Post("/authorize", auth2.AuthorizePost)
|
||||
auth.Post("/token", auth2.Token)
|
||||
auth.Post("/revoke", handlers.Revoke)
|
||||
auth.Post("/introspect", handlers.Introspect)
|
||||
auth.Post("/revoke", auth2.Revoke)
|
||||
auth.Post("/introspect", auth2.Introspect)
|
||||
auth.Post("/verify/sms", handlers.SmsCode)
|
||||
|
||||
// 用户
|
||||
@@ -35,8 +53,8 @@ func ApplyRouters(app *fiber.App) {
|
||||
// 套餐
|
||||
resource := api.Group("/resource")
|
||||
resource.Post("/all", handlers.AllActiveResource)
|
||||
resource.Post("/list/short", handlers.ListResourceShort)
|
||||
resource.Post("/list/long", handlers.ListResourceLong)
|
||||
resource.Post("/list/short", handlers.PageResourceShort)
|
||||
resource.Post("/list/long", handlers.PageResourceLong)
|
||||
resource.Post("/create", handlers.CreateResource)
|
||||
resource.Post("/price", handlers.ResourcePrice)
|
||||
resource.Post("/statistics/free", handlers.StatisticResourceFree)
|
||||
@@ -72,7 +90,7 @@ func ApplyRouters(app *fiber.App) {
|
||||
proxy.Post("/online", handlers.ProxyReportOnline)
|
||||
proxy.Post("/offline", handlers.ProxyReportOffline)
|
||||
proxy.Post("/update", handlers.ProxyReportUpdate)
|
||||
proxy.Post("/register", handlers.ProxyRegisterBaiYin)
|
||||
proxy.Post("/register/baidyin", handlers.ProxyRegisterBaiYin)
|
||||
|
||||
// 节点
|
||||
edge := api.Group("/edge")
|
||||
@@ -82,15 +100,35 @@ func ApplyRouters(app *fiber.App) {
|
||||
// 前台
|
||||
inquiry := api.Group("/inquiry")
|
||||
inquiry.Post("/create", handlers.CreateInquiry)
|
||||
|
||||
// 回调
|
||||
callbacks := app.Group("/callback")
|
||||
callbacks.Get("/identify", handlers.IdentifyCallbackNew)
|
||||
|
||||
// 临时
|
||||
if env.RunMode == env.RunModeDev {
|
||||
debug := app.Group("/debug")
|
||||
debug.Get("/sms/:phone", handlers.DebugGetSmsCode)
|
||||
debug.Get("/proxy/register", handlers.DebugRegisterProxyBaiYin)
|
||||
}
|
||||
|
||||
// 管理员接口路由
|
||||
func adminRouter(api fiber.Router) {
|
||||
api = api.Group("/admin")
|
||||
|
||||
// user 用户
|
||||
var user = api.Group("/user")
|
||||
user.Post("/page", handlers.PageUserByAdmin)
|
||||
user.Post("/bind", handlers.BindAdmin)
|
||||
|
||||
// resource 套餐
|
||||
var resource = api.Group("/resource")
|
||||
resource.Post("/short/page", handlers.PageResourceShortByAdmin)
|
||||
resource.Post("/long/page", handlers.PageResourceLongByAdmin)
|
||||
|
||||
// batch 批次
|
||||
var usage = api.Group("batch")
|
||||
usage.Post("/page", handlers.PageBatchByAdmin)
|
||||
|
||||
// channel 通道
|
||||
var channel = api.Group("/channel")
|
||||
channel.Post("/page", handlers.PageChannelsByAdmin)
|
||||
|
||||
// trade 交易
|
||||
var trade = api.Group("trade")
|
||||
trade.Post("/page", handlers.PageTradeByAdmin)
|
||||
|
||||
// bill 账单
|
||||
var bill = api.Group("/bill")
|
||||
bill.Post("/page", handlers.PageBillByAdmin)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user