完成全部后端和管理系统
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
Name: admin-api
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
30
server/internal/admin/handler/admin/AvaLoginhandler.go
Normal file
30
server/internal/admin/handler/admin/AvaLoginhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/JACKYMYPERSON/hldrCenter/config"
|
||||
)
|
||||
|
||||
func AVALogin(cfg *config.Config) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// 核心:获取请求头中的 session_id(与前端发送的头名称一致)
|
||||
sessionID := r.Header.Get("session_id")
|
||||
|
||||
// 处理头不存在的情况(返回空字符串)
|
||||
if sessionID == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(`{"code":401,"msg":"未获取到 session_id,请登录"}`))
|
||||
return
|
||||
}
|
||||
|
||||
// 后续逻辑:验证 sessionID 有效性...
|
||||
// 例如:查询数据库、缓存判断 session 是否有效
|
||||
fmt.Printf("获取到的 session_id:%s\n", sessionID)
|
||||
|
||||
// 响应成功(示例)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"code":0,"msg":"验证成功"}`))
|
||||
}
|
||||
}
|
||||
89
server/internal/admin/handler/admin/adminloginhandler.go
Normal file
89
server/internal/admin/handler/admin/adminloginhandler.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/JACKYMYPERSON/hldrCenter/config"
|
||||
"github.com/JACKYMYPERSON/hldrCenter/internal/admin/internal/logic/admin"
|
||||
"github.com/JACKYMYPERSON/hldrCenter/internal/admin/internal/model"
|
||||
"github.com/JACKYMYPERSON/hldrCenter/internal/admin/internal/types"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func AdminLogin(cfg *config.Config) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.LoginReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, fmt.Errorf("参数解析失败:%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 验证必填参数
|
||||
if req.Username == "" {
|
||||
httpx.ErrorCtx(r.Context(), w, errors.New("用户名不能为空"))
|
||||
return
|
||||
}
|
||||
if req.Password == "" {
|
||||
httpx.ErrorCtx(r.Context(), w, errors.New("密码不能为空"))
|
||||
return
|
||||
}
|
||||
|
||||
mysqlCfg := cfg.MySQL
|
||||
dsn := fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local",
|
||||
mysqlCfg.Username,
|
||||
mysqlCfg.Password,
|
||||
mysqlCfg.Host,
|
||||
mysqlCfg.Port,
|
||||
mysqlCfg.Database,
|
||||
mysqlCfg.Charset,
|
||||
)
|
||||
fmt.Println("接收到articlePost请求")
|
||||
conn := sqlx.NewSqlConn("mysql", dsn)
|
||||
AdminModel := model.NewAdminModel(conn)
|
||||
|
||||
l := admin.NewLoginAdminLogic(r.Context(), cfg, AdminModel)
|
||||
|
||||
clientIP := GetClientIP(r)
|
||||
resp, err := l.LoginAdmin(&req, clientIP)
|
||||
if err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
} else {
|
||||
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetClientIP(r *http.Request) string {
|
||||
// 1. 优先从代理头获取(如果经过反向代理,如Nginx)
|
||||
ip := r.Header.Get("X-Forwarded-For")
|
||||
if ip != "" {
|
||||
// X-Forwarded-For 可能包含多个IP(客户端IP, 代理1IP, 代理2IP...),取第一个
|
||||
parts := strings.Split(ip, ",")
|
||||
if len(parts) > 0 {
|
||||
ip = strings.TrimSpace(parts[0])
|
||||
if ip != "" {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 其次从 X-Real-IP 获取(部分代理会设置此头)
|
||||
ip = r.Header.Get("X-Real-IP")
|
||||
if ip != "" {
|
||||
return ip
|
||||
}
|
||||
|
||||
// 3. 最后从 RemoteAddr 获取(原始客户端IP,可能包含端口)
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
// 若解析失败,直接返回 RemoteAddr(可能包含端口)
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return ip
|
||||
}
|
||||
136
server/internal/admin/internal/logic/admin/adminloginlogic.go
Normal file
136
server/internal/admin/internal/logic/admin/adminloginlogic.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Code scaffolded by goctl. Safe to edit.
|
||||
// goctl 1.9.1
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/JACKYMYPERSON/hldrCenter/config"
|
||||
"github.com/JACKYMYPERSON/hldrCenter/internal/admin/internal/model"
|
||||
"github.com/JACKYMYPERSON/hldrCenter/internal/admin/internal/types"
|
||||
"github.com/JACKYMYPERSON/hldrCenter/util/auth"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type LoginAdminLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
cfg *config.Config
|
||||
model model.AdminModel
|
||||
}
|
||||
|
||||
func NewLoginAdminLogic(ctx context.Context, cfg *config.Config, model model.AdminModel) *LoginAdminLogic {
|
||||
return &LoginAdminLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
model: model,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LoginAdminLogic) LoginAdmin(req *types.LoginReq, ip string) (resp *types.LoginResp, err error) {
|
||||
// 1. 验证请求参数
|
||||
if err := l.validateReq(req); err != nil {
|
||||
return &types.LoginResp{
|
||||
Code: 1,
|
||||
Msg: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 2. 从数据库查询用户(通过 admin 表模型查询,符合 go-zero 分层规范)
|
||||
admin, err := l.model.FindOneByUsername(l.ctx, req.Username)
|
||||
if err != nil {
|
||||
// 处理查询错误:区分"用户不存在"和"数据库异常"
|
||||
if err == model.ErrNotFound {
|
||||
return &types.LoginResp{
|
||||
Code: 1,
|
||||
Msg: "用户名或密码错误",
|
||||
}, nil
|
||||
}
|
||||
// 数据库异常(记录日志,返回通用错误)
|
||||
l.Logger.Errorf("查询管理员失败: %v, username: %s", err, req.Username)
|
||||
return &types.LoginResp{
|
||||
Code: 500,
|
||||
Msg: "系统内部错误,请稍后再试",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 3. 检查账号状态(1=启用,0=禁用,与表结构一致)
|
||||
if admin.Status != 1 {
|
||||
return &types.LoginResp{
|
||||
Code: 1,
|
||||
Msg: "账号已被禁用,请联系管理员",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 4. 验证密码(数据库存储 bcrypt 加密后的密码)
|
||||
if _, err := VerifyPassword(req.Password, admin.Password); err != nil {
|
||||
return &types.LoginResp{
|
||||
Code: 1,
|
||||
Msg: "用户名或密码错误",
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, err := auth.CreateSession(int(admin.Id), "Kehuduan", ip, 2*time.Hour)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.LoginResp{
|
||||
Code: 0,
|
||||
Msg: "登录成功",
|
||||
Session: session.SessionID,
|
||||
Data: types.AdminInfoResq{
|
||||
Id: admin.Id,
|
||||
Username: NullStringToString(admin.Username),
|
||||
Role: NullStringToString(admin.Role),
|
||||
CoverUrl: NullStringToString(admin.CoverUrl),
|
||||
Intro: NullStringToString(admin.Intro),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 验证请求参数
|
||||
func (l *LoginAdminLogic) validateReq(req *types.LoginReq) error {
|
||||
if req.Username == "" {
|
||||
return errors.New("用户名不能为空")
|
||||
}
|
||||
if req.Password == "" {
|
||||
return errors.New("密码不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyPassword(plainPassword, hashedPassword string) (bool, error) {
|
||||
plainBytes := []byte(plainPassword)
|
||||
hashedBytes := []byte(hashedPassword)
|
||||
|
||||
err := bcrypt.CompareHashAndPassword(hashedBytes, plainBytes)
|
||||
if err == nil {
|
||||
return true, nil // 匹配成功(密码正确)
|
||||
} else if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return false, nil // 匹配失败(密码错误)
|
||||
} else {
|
||||
return false, err // 其他异常(如哈希格式错误、内存不足等)
|
||||
}
|
||||
}
|
||||
|
||||
func NullStringToString(ns sql.NullString) string {
|
||||
if ns.Valid {
|
||||
return ns.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func StringToNullString(s string) sql.NullString {
|
||||
if s != "" {
|
||||
return sql.NullString{String: s, Valid: true}
|
||||
}
|
||||
return sql.NullString{Valid: false}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/builder"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
@@ -29,6 +30,7 @@ type (
|
||||
FindOne(ctx context.Context, id int64) (*Admin, error)
|
||||
Update(ctx context.Context, data *Admin) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
FindOneByUsername(ctx context.Context, username string) (*Admin, error)
|
||||
}
|
||||
|
||||
defaultAdminModel struct {
|
||||
@@ -88,6 +90,37 @@ func (m *defaultAdminModel) Update(ctx context.Context, data *Admin) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *defaultAdminModel) FindOneByUsername(ctx context.Context, username string) (*Admin, error) {
|
||||
// 1. 构建查询语句:使用model的table字段(避免硬编码表名),查询所有字段(与Admin结构体对应)
|
||||
// 字段顺序与Admin结构体字段顺序一致,确保映射正确
|
||||
query := fmt.Sprintf(`
|
||||
SELECT
|
||||
id, username, password, cover_url, intro, create_time, update_time, role, status
|
||||
FROM %s
|
||||
WHERE username = ?
|
||||
LIMIT 1
|
||||
`, m.table) // 使用m.table替代硬编码"admin",适配表名可能的变化
|
||||
|
||||
var admin Admin
|
||||
err := m.conn.QueryRowCtx(ctx, &admin, query, username)
|
||||
if err != nil {
|
||||
// 3. 错误处理:区分"记录不存在"和"查询异常"
|
||||
if err == sql.ErrNoRows {
|
||||
// 返回自定义的"记录不存在"错误,上层可明确判断
|
||||
return nil, err
|
||||
}
|
||||
// 其他错误(如SQL语法错误、连接异常等),记录日志并返回
|
||||
logx.WithContext(ctx).Errorf(
|
||||
"failed to find admin by username: %s, err: %v, query: %s",
|
||||
username, err, query,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. 查询成功,返回管理员信息
|
||||
return &admin, nil
|
||||
}
|
||||
|
||||
func (m *defaultAdminModel) tableName() string {
|
||||
return m.table
|
||||
}
|
||||
|
||||
@@ -85,3 +85,23 @@ type UpdateAdminReq struct {
|
||||
type UpdateAdminResp struct {
|
||||
BaseResp
|
||||
}
|
||||
|
||||
type LoginReq struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type AdminInfoResq struct {
|
||||
Id int64 `json:"id"` // 用户ID
|
||||
Username string `json:"username"` // 用户名
|
||||
CoverUrl string `json:"cover_url"` // 封面URL
|
||||
Intro string `json:"intro"` // 简介
|
||||
Role string `json:"role"` // 角色(super_admin/normal_admin)
|
||||
}
|
||||
|
||||
type LoginResp struct {
|
||||
Code int `json:"code"` // 响应码(0=成功)
|
||||
Msg string `json:"msg"` // 提示信息
|
||||
Session string `json:"session"`
|
||||
Data AdminInfoResq `json:"data"` // 成功返回的用户信息
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user