goweb实现用户管理系统
用户后台管理系统功能描述
登录功能
- 支持用户通过邮箱密码和密码进行登录。
- 对输入的邮箱和密码进行验证,确保用户信息的正确性。
- 登录成功后,更新用户的今日登录统计信息,并将用户信息存入会话(cookie)中,便于后续操作。
- 提供友好的错误提示,如"用户不存在""密码错误""用户已被禁用"等,帮助用户了解登录失败的原因。

go
package service
import (
"UserManager/src/mapper"
"UserManager/src/utils"
"fmt"
"golang.org/x/crypto/bcrypt"
)
type LoginService struct {
Mapper *mapper.LoginMapper
}
func NewLoginService(lm *mapper.LoginMapper) *LoginService {
return &LoginService{
Mapper: lm,
}
}
// 用户登录
func (ls *LoginService) LoginUser(email string, password string) (string, error) {
user, err := ls.Mapper.GetUserByEmail(email)
if err != nil || user == nil {
return "", fmt.Errorf("用户不存在")
}
//验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return "", fmt.Errorf("密码错误")
}
fmt.Println("hhh", user)
if user.Status == 0 {
return "", fmt.Errorf("用户已被禁用")
}
//生成JWT
token, err := utils.GenerateToken(user)
if err != nil {
return "", fmt.Errorf("生成令牌失败")
}
return token, nil
}
注册功能
- 允许用户输入邮箱、验证码,密码及确认密码进行注册。
- 对邮箱进行唯一性校验,避免重复注册,并发送验证码。
- 检查两次输入的密码是否一致,确保用户密码的准确性。
- 使用 bcrypt 算法对用户密码进行加密存储,保障用户密码的安全性。
- 注册成功后,自动增加今日新增人数统计。

go
package service
import (
"UserManager/src/mapper"
"UserManager/src/models"
"UserManager/src/utils"
"fmt"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"regexp"
)
// VerificationInfo 保存验证码及其过期时间
type RegisterService struct {
Mapper *mapper.RegisterMapper
EmailService *utils.EmailService
VerificationService *utils.VerificationService
}
func NewRegisterService(rm *mapper.RegisterMapper, emailService *utils.EmailService, vService *utils.VerificationService) *RegisterService {
return &RegisterService{
Mapper: rm,
EmailService: emailService,
VerificationService: vService,
}
}
// SendVerificationCode 生成验证码,并使用 Redis 存储(有效期5分钟)后发送邮件
func (rs *RegisterService) SendVerificationCode(email string) error {
code, err := rs.VerificationService.GenerateAndStoreCode(email)
if err != nil {
return err
}
subject := "您的验证码"
body := fmt.Sprintf("您的验证码为:%s,请勿泄露于他人!该验证码5分钟内有效!如非本人操作,请忽略此邮件!。", code)
return rs.EmailService.SendEmail(email, subject, body)
}
// 用户注册
func (rs *RegisterService) RegisterUser(email, inputCode, password, passwordConfirm string) error {
if !isValidPassword(password) {
return fmt.Errorf("密码必须是8-12位字母和数字组合")
}
if password != passwordConfirm {
return fmt.Errorf("两次密码输入不一致")
}
// 验证验证码
if err := rs.VerificationService.VerifyCode(email, inputCode); err != nil {
return err
}
// 检查邮箱是否已注册
if user, _ := rs.Mapper.GetUserByEmail(email); user != nil {
return fmt.Errorf("邮箱已注册")
}
// 加密密码
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("密码加密失败:%v", err)
}
// 插入新用户
newUser := &models.User{
Email: email,
PasswordHash: string(hashed),
Nickname: "user_" + uuid.New().String()[:8],
}
if err := rs.Mapper.InsertUser(newUser); err != nil {
return fmt.Errorf("用户注册失败:%v", err)
}
return nil
}
// isValidPassword 校验密码是否满足8-12位字母和数字组合
func isValidPassword(password string) bool {
// 1. 检查长度是否为8-12位
if len(password) < 8 || len(password) > 12 {
return false
}
// 2. 检查是否只包含字母和数字
validChars, err := regexp.MatchString(`^[0-9A-Za-z]+$`, password)
if err != nil || !validChars {
return false
}
// 3. 检查是否至少包含一个字母
hasLetter, err := regexp.MatchString(`[A-Za-z]`, password)
if err != nil || !hasLetter {
return false
}
// 4. 检查是否至少包含一个数字
hasDigit, err := regexp.MatchString(`\d`, password)
if err != nil || !hasDigit {
return false
}
return true
}
用户管理
用户列表展示

- 以分页的形式展示系统中的用户列表,每页显示固定数量的用户记录。
- 提供用户的基本信息,包括用户名、角色、状态、邮箱、头像、注册时间等,方便管理员快速了解用户情况。
- 根据当前页码和总记录数,动态计算并显示分页导航栏,方便管理员切换页面查看不同用户数据。
用户搜索
- 支持管理员通过用户名关键字进行用户搜索,快速定位目标用户。
- 搜索结果同样以分页形式展示,方便查看大量用户数据。
- 提供搜索结果的总记录数、总页数等信息,帮助管理员了解搜索结果的范围。
用户创建

**
- 管理员可以输入用户名、密码、邮箱、角色、状态等信息创建新用户。
- 对输入的用户信息进行校验,如用户名是否已存在、必填项是否填写等。
- 支持上传用户头像,并对上传的文件进行类型、大小校验,确保头像的合法性。 创建成功后,自动更新今日新增人数统计。
用户更新

- 管理员可以对现有用户的信息进行修改,包括用户名、密码、邮箱、角色、状态、头像等。
- 在修改密码时,只有当用户输入了新密码时才会进行密码更新,并且会对新密码进行加密处理。
- 对修改后的用户信息进行校验,如用户名是否被其他用户占用等。
- 更新成功后,返回更新后的用户头像路径等信息。
用户删除

- 管理员可以删除指定的用户,但不允许删除当前登录用户自己。
- 删除用户时,同时删除该用户的头像文件(如果头像不是默认头像)。
- 删除成功后,自动更新今日被删除人数统计。
go
package service
import (
"UserManager/src/mapper"
"UserManager/src/models"
"UserManager/src/utils"
"errors"
"fmt"
"golang.org/x/crypto/bcrypt"
"io"
)
type UserService struct {
Mapper *mapper.UserMapper
}
func NewUserService(hm *mapper.UserMapper) *UserService {
return &UserService{
Mapper: hm,
}
}
// GetUsers 支持分页 + 搜索 + 状态筛选
func (us *UserService) GetUsers(keyword, statusStr string, page, pageSize int) ([]*models.User, int, error) {
// 计算 offset
offset := (page - 1) * pageSize
// 委托 Mapper 执行查询并返回总数
return us.Mapper.QueryUsersWithPage(keyword, statusStr, offset, pageSize)
}
// 新增用户
func (us *UserService) CreateUser(email, passwordHash, nickname string, avatarFile io.Reader, avatarFileName string, role, status int) (*models.User, error) {
// 上传头像到 OSS
avatarURL, err := utils.UploadFileToOSS(avatarFile, avatarFileName)
if err != nil {
return nil, err
}
// 检查邮箱是否已注册
if user, _ := us.Mapper.GetUserByEmail(email); user != nil {
return nil, errors.New("邮箱已注册,请使用其他邮箱")
}
// 加密密码
hashed, err := bcrypt.GenerateFromPassword([]byte(passwordHash), bcrypt.DefaultCost)
if err != nil {
return nil, errors.New("密码加密失败")
}
user := &models.User{
Email: email,
PasswordHash: string(hashed),
Nickname: nickname,
AvatarUrl: avatarURL,
Role: role,
Status: status,
}
if err := us.Mapper.CreateUser(user); err != nil {
return nil, err
}
return user, nil
}
func (us *UserService) UpdateUser(id int, email, passwordHash, nickname string, avatarFile io.Reader, avatarFileName string, role, status int) (*models.User, error) {
// 1. 先取出数据库中的原用户
user, err := us.Mapper.GetUserByID(id)
if err != nil {
return nil, err
}
// 2. 如果邮箱被修改,检查是否重复
if email != user.Email {
if existing, _ := us.Mapper.GetUserByEmail(email); existing != nil {
return nil, errors.New("邮箱已注册,请使用其他邮箱")
}
}
// 3. 更新基本字段
user.Email = email
user.Nickname = nickname
user.Role = role
user.Status = status
// 4. 如果前端传了非空密码,才加密并更新
if passwordHash != "" {
if !isValidPassword(passwordHash) {
return nil, fmt.Errorf("密码必须是8-12位字母和数字组合")
}
hashed, err := bcrypt.GenerateFromPassword([]byte(passwordHash), bcrypt.DefaultCost)
if err != nil {
return nil, errors.New("密码加密失败")
}
user.PasswordHash = string(hashed)
}
// 5. 如果前端上传了新头像,才上传并更新 URL
if avatarFile != nil {
url, err := utils.UploadFileToOSS(avatarFile, avatarFileName)
if err != nil {
return nil, err
}
user.AvatarUrl = url
}
// 6. 写回数据库
if err := us.Mapper.UpdateUser(user); err != nil {
return nil, err
}
return user, nil
}
// 根据用户 ID 获取用户信息
func (us *UserService) GetUserByID(id int) (*models.User, error) {
return us.Mapper.GetUserByID(id)
}
// 删除用户信息
func (us *UserService) DeleteUser(id int) error {
return us.Mapper.DeleteUser(id)
}
用户统计与数据展示
首页统计信息展示
- 在系统首页展示一些关键的用户统计数据,如总用户数、本月登录人次、注销用户数量等。
提供登录增长率、用户增长率、注销用户增长率等指标,帮助管理员了解系统的整体发展趋势 - 展示过去 30天的登录趋势图表,以日期为维度展示每日的登录次数,方便管理员观察登录量的变化趋势。
- 相关的统计信息(如今日登录人数、今日新增人数、今日被删除人数)会随着用户的登录、注册、删除等操作实时更新,确保数据的准确性和时效性。
go
package service
import (
"UserManager/src/mapper"
"time"
)
type HomeService struct {
Mapper *mapper.HomeMapper
}
// TrendData 用于返回给前端的访问趋势数据
type TrendData struct {
Date string `json:"date"`
Count int `json:"count"`
}
// 仪表数据
type DashboardData struct {
RegisteredUsers int `json:"registered_users"`
Visits int `json:"visits"`
DeactivatedUsers int `json:"deactivated_users"`
RegisteredUsersGrowth float64 `json:"registered_users_growth"`
VisitsGrowth float64 `json:"visits_growth"`
DeactivatedUsersGrowth float64 `json:"deactivated_users_growth"`
}
func NewHomeService(hm *mapper.HomeMapper) *HomeService {
return &HomeService{
Mapper: hm,
}
}
// 获取仪表盘统计数据
func (hs *HomeService) GetDashboardStats() (DashboardData, error) {
// 获取当前时间
now := time.Now()
currentUsers, err := hs.Mapper.CountRegisteredUsers(now)
if err != nil {
return DashboardData{}, err
}
currentVisits, err := hs.Mapper.CountVisits(time.Now())
if err != nil {
return DashboardData{}, err
}
currentDeactivated, err := hs.Mapper.CountDeactivatedUsers(time.Now())
if err != nil {
return DashboardData{}, err
}
//获取上个月的数据
// 获取当前年份和月份
year, month, _ := now.Date()
// 计算上个月的年份和月份
if month == time.January {
year--
month = time.December
} else {
month--
}
// 获取上个月的第一天
firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, now.Location())
// 上个月的最后一天是本月第一天的前一天
lastMonth := firstOfMonth.AddDate(0, 1, -1)
previousUsers, err := hs.Mapper.CountRegisteredUsers(lastMonth)
if err != nil {
return DashboardData{}, err
}
previousVisits, err := hs.Mapper.CountVisits(lastMonth)
if err != nil {
return DashboardData{}, err
}
previousDeactivated, err := hs.Mapper.CountDeactivatedUsers(lastMonth)
if err != nil {
return DashboardData{}, err
}
// 计算增长率
usersGrowth := calculateGrowth(previousUsers, currentUsers)
visitsGrowth := calculateGrowth(previousVisits, currentVisits)
deactivatedGrowth := calculateGrowth(previousDeactivated, currentDeactivated)
return DashboardData{
RegisteredUsers: currentUsers,
Visits: currentVisits,
DeactivatedUsers: currentDeactivated,
RegisteredUsersGrowth: usersGrowth,
VisitsGrowth: visitsGrowth,
DeactivatedUsersGrowth: deactivatedGrowth,
}, nil
}
// calculateGrowth 计算增长率
func calculateGrowth(previous, current int) float64 {
if previous == 0 {
if current > 0 {
return 100.0 // 从0增长到正数,视为100%增长
}
return 0.0 // 无变化
}
return (float64(current-previous) / float64(previous)) * 100
}
func (hs *HomeService) AddVisitCounts(userID int) error {
err := hs.Mapper.AddVisitCounts(userID)
if err != nil {
return err
}
return nil
}
// 根据传入天数获取访问趋势数据
func (hs *HomeService) GetAccessTrends(days int) ([]TrendData, error) {
// 计算起始时间(包含当天)
startTime := time.Now().AddDate(0, 0, -days+1)
visits, err := hs.Mapper.GetVisitsFrom(startTime)
if err != nil {
return nil, err
}
// 初始化每天的访问计数(key 为日期字符串)
trendMap := make(map[string]int)
for i := 0; i < days; i++ {
dateStr := time.Now().AddDate(0, 0, -i).Format("2006-01-02")
trendMap[dateStr] = 0
}
// 遍历访问记录进行计数
for _, visit := range visits {
dateStr := visit.VisitTime.Format("2006-01-02")
trendMap[dateStr]++
}
// 按时间顺序组织返回数据(例如从最早到最新)
var trends []TrendData
for i := days - 1; i >= 0; i-- {
date := time.Now().AddDate(0, 0, -i).Format("2006-01-02")
trends = append(trends, TrendData{
Date: date,
Count: trendMap[date],
})
}
return trends, nil
}
具体代码看我github,https://github.com/cxzgit/UserManager
另外utils下的oss.go这里你配一下就好了
go
package utils
import (
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"io"
"time"
)
// UploadFileToOSS 上传文件到 OSS,并返回公开访问的 URL
func UploadFileToOSS(file io.Reader, fileName string) (string, error) {
endpoint := ""
accessKeyID := ""
accessKeySecret := ""
bucketName := ""
client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
if err != nil {
return "", fmt.Errorf("failed to create OSS client: %w", err)
}
bucket, err := client.Bucket(bucketName)
if err != nil {
return "", fmt.Errorf("failed to get OSS bucket: %w", err)
}
// 生成唯一的对象 key(例如:avatars/时间戳_原文件名)
objectKey := fmt.Sprintf("avatars/%d_%s", time.Now().UnixNano(), fileName)
if err := bucket.PutObject(objectKey, file); err != nil {
return "", fmt.Errorf("failed to upload file: %w", err)
}
// 假设 bucket 为公共读,构造公开访问的 URL
ossURL := fmt.Sprintf("https://%s.%s/%s", bucketName, endpoint, objectKey)
return ossURL, nil
}