Go web 用户管理系统(部分前后端代码+项目功能展示)

前言

本篇文章主要讲述一个前后端不分离的用户管理系统。后端基于golang语言,前端基于html5和tailwind css框架。

一、功能介绍

1.用户登录注册功能

2.展示主界面

3.管理员编辑用户功能

4.管理员删除用户功能

5.个人信息编辑功能(头像上传,信息修改)

主要功能分为上述五个部分,其他小的细节不再赘述

二、项目架构

项目结构主要基于MVC三层架构模式

主要分为三层:

第一层:controller层:用于接收前端传过来的数据

第二层:service层:用于数据逻辑的处理,为controller层服务

第三层:dao层:主要用于项目与数据库的交互,为service层服务

除此之外,项目还有专门存放参数的model层;存放用来响应前端数的据的pkg层;存放前端页面代码的view层;存放图片的uploads层

具体如下图所示:

三、部分重要代码展示

1.用户登录功能:

接收前端数据 ——>交给service层处理——>进行数据库查询判断——>逐步返回结果给service层、controller层——>将查询结果返回给前端页面。

1.controller层代码如下:

package user

import (
	"html/template"
	"log"
	"net/http"
	"time"
	"webb/model"
	"webb/model/param"
	"webb/service"
)

// 登录
func Login(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		t, err := template.ParseFiles("view/pages/user/login.html")
		if err != nil {
			log.Fatal(err)
		}
		err = t.Execute(w, nil)
		if err != nil {
			return
		}
	} else {
		t, err := template.ParseFiles("view/pages/user/login.html")
		if err != nil {
			log.Fatal(err)
		}
		err = r.ParseForm()
		if err != nil {
			log.Fatal(err)
		}
		account := r.FormValue("account")
		password := r.FormValue("password")
		u := model.User{
			Account:  account,
			Password: password,
		}

		// 交给service层处理
		//service.Check(u)
		if !service.CheckAccountFormat(u.Account) {
			data := param.LoginPageData{"账号格式不正确"}
			err := t.Execute(w, data)
			if err != nil {
				return
			}
			return
		} else if !service.CheckAccountExists(u.Account) {
			data := param.LoginPageData{"账号不存在,请重新输入"}
			err := t.Execute(w, data)
			if err != nil {
				return
			}
			return
		} else if !service.CheckPassword(u.Account, u.Password) {
			data := param.LoginPageData{"账号或者密码不正确"}
			err := t.Execute(w, data)
			if err != nil {
				return
			}
			return
		} else {
			//设置cookie,保存登录的账号
			http.SetCookie(w, &http.Cookie{
				Name:     "account",
				Value:    u.Account,
				Path:     "/",
				Expires:  time.Now().Add(time.Hour * 24),
				HttpOnly: true,
				Secure:   true,
			})
			http.Redirect(w, r, "/home", http.StatusFound) //告诉浏览器要跳转页面
		}
	}

}

2. service层代码如下:

package service

import (
	"fmt"
	"regexp"
	"webb/dao"
)

// 检查账号格式问题
func CheckAccountFormat(account string) bool {
	str := account
	pattern := `^[0-9a-zA-Z]{6,18}$`
	re := regexp.MustCompile(pattern)
	if !re.MatchString(str) {
		fmt.Println("账号格式不符合规范")
		return false
	}

	return true
}

// 检查账号存不存在
func CheckAccountExists(account string) bool {
	err := dao.QueryUserAccount(account)
	if err != nil {
		fmt.Println("账号不存在")
		return false
	}
	return true
}

// 检查密码正不正确
func CheckPassword(account string, password string) bool {
	// 从数据库里面取
	psd, err := dao.QueryUserPasswordByAccount(account)
	if err != nil {
		fmt.Println("查询失败")
		return false
	}
	if psd != password {
		fmt.Println("密码不正确")
		return false
	}
	return true
}

3.dao层部分代码如下:

// 查询密码是否对应当前账号
func QueryUserPasswordByAccount(account string) (psd string, err error) {
	sqlStr := "select password from user where account = ?"
	var u model.User
	if err := db.QueryRow(sqlStr, account).Scan(&u.Password); err != nil {
		return "", err
	}
	return u.Password, nil
}

// 查询账号存不存在
func QueryUserAccount(account string) (err error) {
	sqlStr := "select account from user where account = ?"
	var u model.User
	err = db.QueryRow(sqlStr, account).Scan(&u.Account)
	if err != nil {
		return err
	}
	return nil
}

2.当前登录用户展示以及用户列表展示功能:

用cookie获取当前登录用户账号——>在dao层查询所有用户的信息——>将查询之后的结果逐步返回给controller层——>将cookie值和用户数据绑定到结构体上传给前端——>前端接收数据并且显示在页面上

1.controller层代码如下:

package user

import (
	"html/template"
	"log"
	"net/http"
	"webb/model"
	"webb/model/param"
	"webb/service/QueueUserMess"
)

func Home(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("view/pages/public/home.html")

	//用cookie获得登录用户的账号
	c, e := r.Cookie("account")
	if e != nil {
		log.Fatal(e)
	}

	//用一个值接收查询所有用户账号返回的切片
	users := QueueUserMess.QueryAllMess()

	//将cookie值放到结构体中
	user := model.User{
		Account: c.Value,
	}

	CurRole := QueueUserMess.QueryRoleByCount(c.Value)
	//创建一个结构体存放当前账号和所有用户的信息
	data := param.HomePageData{
		CurUser:    user,  //当前账号
		UserList:   users, //所有账号的信息
		CurserRole: CurRole,
	}

	err := t.Execute(w, data)
	if err != nil {
		return
	}
}

2.service部分层代码如下:

package QueueUserMess

import (
	"webb/dao"
	"webb/model"
)

// 查询所有账号信息、
func QueryAllMess() []model.User {
	user, _ := dao.QueryAllAccount()
	return user
}

// 根据账号寻找role
func QueryRoleByCount(account string) (role int) {
	r := dao.QueryRoleByAccount(account)
	return r
}

3.dao层部分代码如下:

// 查询所有用户的账号信息
func QueryAllAccount() ([]model.User, error) {
	sqlStr := "select * from user"
	var u model.User
	rows, err := db.Query(sqlStr)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	defer func(rows *sql.Rows) {
		err := rows.Close()
		if err != nil {

		}
	}(rows)
	var user []model.User
	//循环读取数据
	for rows.Next() {
		e := rows.Scan(&u.Id, &u.Account, &u.Password, &u.Role, &u.AvatarURL, &u.UserName, &u.Gender, &u.Birthday, &u.Province, &u.Address, &u.City)
		if e != nil {
			log.Fatal("读取数据失败-----------", e)
			return user, e
		}
		user = append(user, u)
	}
	return user, nil
}

3.头像上传功能:

解析前端含有头像url表单——>读取表单文件内容——>将读取的图片url (用户头像路径) 存到项目中——>添加用户头像路径,保存到数据库当中——>保存头像时进行数据库查询url并传给前端进行展示

1.controller层接收前端上传的文件代码如下 :

package file

import (
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"webb/service"
	"webb/service/AddUserMess"
)

func UploadHeadPhoto(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		if err := r.ParseMultipartForm(10 << 20); err != nil {
			http.Error(w, "文件过大或格式错误", http.StatusBadRequest)
			return
		}

		// 限制内存使用并解析表单
		const maxUploadSize = 10 << 20
		if err := r.ParseMultipartForm(maxUploadSize); err != nil {
			fmt.Println("表单解析失败", err)
			http.Error(w, "文件过大或表单解析失败", http.StatusBadRequest)
			return
		}

		//处理头像上传
		file, header, err := r.FormFile("avatar")
		if err != nil {
			fmt.Println("name读取错误", err)
			return
		}
		defer func(file multipart.File) {
			err := file.Close()
			if err != nil {

			}
		}(file)
		fileName := header.Filename

		//生成唯一的文件名
		uniqueName := service.GenerateUniqueFileName(fileName)

		b, e := ioutil.ReadAll(file)
		if e != nil {
			fmt.Println("文件读取错误", err)
		}

		err = ioutil.WriteFile("./uploads/static/imgs/"+uniqueName, b, 0777)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("文件保存成功")
		//t.Execute(w, nil)

		//添加用户头像路径,保存到数据库当中
		c, err := r.Cookie("account")
		if err != nil {
			fmt.Println("获得当前账号失败", err)
			return
		}

		finalName := fmt.Sprintf("http://localhost:8080/uploads/static/imgs/%s", uniqueName)
		err = AddUserMess.AddAvatar(c.Value, finalName)
		if err != nil {
			fmt.Println("保存失败", err)
			return
		}
		fmt.Println("路径添加成功")

	}

}

2.sevice层部分代码如下:

package AddUserMess

import "webb/dao"

func AddAvatar(account, avatarUrl string) error {
	return dao.AddUserAvatar(account, avatarUrl)
}

3.dao层部分数据如下:

// 添加用户头像的url
func AddUserAvatar(account string, avatarUrl string) (err error) {
	sqlStr := "update user set avatarURL = ? where account = ?"
	_, err = db.Exec(sqlStr, avatarUrl, account)
	if err != nil {
		log.Fatal("修改原始头像失败", err)
		return err
	}
	return nil
}

4.controller层在需要保存头像时查询头像的url并伴随用户其他必要信息一起返回给前端页面显示代码如下:

package user

import (
	"html/template"
	"log"
	"net/http"
	"webb/service/QueueUserMess"
)

个人主页,不可编辑
func ShowPage(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" || r.Method == "POST" {
		t, err := template.ParseFiles("view/pages/user/personalHomepage.html")
		if err != nil {
			log.Fatal(err)
			return
		}
		//通过cookie获取用户账号
		c, err := r.Cookie("account")
		if err != nil {
			log.Fatal(err)
		}
		//通过账号获得用户名字, 性别, 生日, 省份, 地址, 头像url
		u := QueueUserMess.PersonalMessQueue(c.Value)
		err = t.Execute(w, u)
		if err != nil {
			return
		}
	}
}

4.前端部分代码:

1.用户登录界面代码如下:

<!-- login.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录页面</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="../static/errShow.js"></script>
</head>
<body class="bg-gray-100 h-screen flex items-center justify-center">

<div class="bg-white p-8 rounded-lg shadow-md max-w-md w-full">
    <h2 class="text-2xl font-bold mb-6 text-gray-800">用户登录</h2>

    <!-- 错误提示 -->
    {{if .Error}}
    <div id="error-message" class="mb-4 p-3 bg-red-100 text-red-700 rounded-md transition-opacity duration-300">
        {{.Error}}
    </div>
    {{end}}

    <form class="space-y-4" action="/login" method="POST">
        <div>
            <label class="block text-gray-700 mb-2">用户名</label>
            <input type="text" name="account"
                   class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                   required>
        </div>
        <div>
            <label class="block text-gray-700 mb-2">密码</label>
            <input type="password" name="password"
                   class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                   required>
        </div>
        <button type="submit"
                class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors">
            登录
        </button>
    </form>
    <p class="mt-4 text-center text-gray-600" >
        没有账号?<a href="/user/register" class="text-blue-500 hover:underline">立即注册</a>
    </p>
</div>

<script>
    const errorDiv = document.getElementById('error-message');
    if (errorDiv) {
        setTimeout(() => {
            errorDiv.style.opacity = '0';
            setTimeout(() => errorDiv.remove(), 300);
        }, 3000);
    }
</script>

</body>
</html>

 2.用户列表页面如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>用户管理系统</title>
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" integrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body class="bg-gray-100">
<!-- 导航栏 -->
<nav class="bg-blue-500 p-4 text-white">
  <div class="container mx-auto flex justify-between items-center">
    <a href="#" class="text-xl font-semibold">用户管理</a>
    <div class="flex items-center space-x-4">
      <!-- 新增当前用户显示 -->
      <div class="flex items-center text-sm">
        <i class="fas fa-user-circle mr-2"></i>
        <span>当前登录: {{.CurUser.Account}}</span>
      </div>
      <a href="#" class="hover:text-blue-200">首页</a>
      <a href="#" class="hover:text-blue-200">设置</a>
      <a href="/logout" class="hover:text-blue-200">退出当前登录</a>
      <a href="/user/personalHomePage" class="hover:text-blue-200">个人主页</a>
    </div>
  </div>
</nav>
<!-- 主内容区域 -->
<div class="container mx-auto mt-8">
  <!-- 用户列表 -->
  <div class="bg-white shadow-md rounded my-6">
    <table class="min-w-max w-full table-auto">
      <thead>
      <tr class="bg-gray-200 text-gray-600 uppercase text-sm leading-normal">
        <th class="py-3 px-6 text-left">ID</th>
        <th class="py-3 px-6 text-left">用户名</th>
        <th class="py-3 px-6 text-center">角色</th>
        <th class="py-3 px-6 text-center">操作</th>
      </tr>
      </thead>
      <tbody class="text-gray-600 text-sm font-light">
      {{range .UserList}}
      <tr class="border-b border-gray-200 hover:bg-gray-100" data-user-id="{{.Id}}">
        <td class="py-3 px-6 text-left whitespace-nowrap">
          <div class="flex items-center">
            <span class="font-medium">{{.Id}}</span>
          </div>
        </td>

        <td class="py-3 px-6 text-left">
          <div class="flex items-center">
            <span>{{.Account}}</span>
          </div>
        </td>
        <td class="py-3 px-6 text-center">
          <span class="bg-purple-200 text-purple-600 py-1 px-3 rounded-full text-xs">{{if .Role}}管理员{{else}}普通用户{{end}} </span>
        </td>
        <td class="py-3 px-6 text-center">
          <div class="flex items-center justify-center space-x-2">
            <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded text-xs"
                    {{ if not $.CurserRole }}style="pointer-events: none;"{{ end }}
                    onclick="if ({{$.CurserRole}}) { handleEditClick('{{.Id}}'); } else { alert('普通用户无权编辑'); }">
              编辑
            </button>
            <button class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded text-xs"
                    {{ if not $.CurserRole }}style="pointer-events: none;"{{ end }}
                    onclick="if ({{$.CurserRole}}) { deleteUser('{{.Id}}'); } else { alert('普通用户无权删除'); }">
              删除
            </button>
          </div>
        </td>
      </tr>
      {{end}}
      <!-- 更多用户行 -->
      </tbody>
    </table>
  </div>

  <!-- 添加用户按钮 -->
  <!--  <div class="flex justify-end">-->
  <!--    <button class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">-->
  <!--      删除用户-->
  <!--    </button>-->
  <!--  </div>-->
</div>

<!-- 管理员进行用户信息编辑 -->
<div id="adminEditModal" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center hidden">
  <div class="bg-white rounded-lg p-6 w-full max-w-md">
    <h3 class="text-xl font-semibold mb-4">编辑用户信息</h3>
    <form id="adminEditForm" action="/edit-user" method="post" class="space-y-4">
      <div>
        <label class="block text-gray-700 mb-2">用户账号</label>
        <input type="text" id="editUsername" name="newAccount" class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
      </div>
      <div>
        <label class="block text-gray-700 mb-2">用户密码</label>
        <input type="text" id="editPassword" name="newPassword" class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
      </div>
      <div class="flex justify-end gap-3">
        <button type="button" id="closeEditModal" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">
          取消
        </button>
        <button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
          保存
        </button>
      </div>
    </form>
  </div>
</div>
</body>
</html>



<!-- 用ajax请求返回id完成删除操作-->
<script>
  function deleteUser(userId) {
    //const userId = $("#deleteId").val(); // 获取要删除的ID
    console.log(userId,"-----")

    // 通过 data-user-id 属性找到对应的行
    const row = document.querySelector(`tr[data-user-id="${userId}"]`);
    if (!row) {
      alert('未找到对应的用户行!');
      return;
    }

    var formData = new FormData();
    formData.append('id', userId);
    $.ajax({
      type: "POST",
      url: "/user/delete",  // 建议改成真正的删除接口如"/user/delete"
      data: formData,
      processData: false, // 告诉 jQuery 不要对数据进行处理
      contentType: false, // 告诉 jQuery 不要设置 contentType
      success: function(response) {
        console.log("后端响应"+response);
        console.log("ddddddddddddddddddddddddddddddd");
        console.log("response 类型:", typeof response); // 检查是否为 object
        if (response.success && response) {
          console.log("删除成功");
          // 添加删除动画:渐变透明度后移除元素
          row.style.transition = 'opacity 0.5s';
          row.style.opacity = '0';
          setTimeout(() => row.remove(), 500);
          location.reload();
          alert("删除成功")

        } else {
          // const message = response?.message || "未知错误";
          console.log(response.message);
          alert("删除失败: " + response.message);
        }
      },
      error: function(xhr) {
        let errorMessage = "请求失败";
        try {
          const response = JSON.parse(xhr.responseText);
          errorMessage += `: ${response.message || response.code}`;
        } catch (e) {
          errorMessage += `,状态码: ${xhr.status}`;
        }
        console.log(errorMessage);
        alert(errorMessage);
      }
    });
  }
</script>

<!--用ajax实现点击编辑将id传过去-->
<script>
  function handleEditClick(userId) {
    // 显示编辑模态框
    $('#adminEditModal').removeClass('hidden');

    // 获取当前行的用户信息
    const row = document.querySelector(`tr[data-user-id="${userId}"]`);
    if (!row) {
      alert('未找到对应的用户行!');
      return;
    }

    // 获取当前行的用户名
    const username = row.querySelector('td:nth-child(2) span').textContent;


    // 填充表单
    $('#editUsername').val(username);
    {{range .UserList}}
    $('#editPassword').val({{.Password}});// 密码字段留空
    {{end}}

    // 保存当前编辑的用户ID
    $('#adminEditForm').data('userId', userId);
  }

  // 关闭编辑模态框
  $('#closeEditModal').click(function() {
    $('#adminEditModal').addClass('hidden');
  });

  // 处理编辑表单提交
  $('#adminEditForm').submit(function(e) {
    e.preventDefault();

    const userId = $(this).data('userId');
    const newAccount = document.getElementById('editUsername').value;
    const passWord = document.getElementById('editPassword').value;
    var formData = new FormData(this);
    formData.append('id', userId);
    // formData.append('newAccount', newAccount);
    // formData.append('password', password);
    const params = new URLSearchParams({
      id : userId,
      newAccount: newAccount,
      password: passWord,
    });
    console.log(formData);
    $.ajax({
      method: "POST",
      url: `/edit-user?${params.toString()}`,
      data: formData,
      processData: false,
      contentType: false,
      headers: false,
      success: function(response) {
        if (response.success) {
          alert('用户信息修改成功');
          $('#adminEditModal').addClass('hidden');
          location.reload(); // 刷新页面以显示更新后的信息
        } else {
          alert('修改失败: ' + (response.message || '未知错误'));
        }
      },
      error: function(xhr) {
        let errorMessage = "请求失败";
        try {
          const response = JSON.parse(xhr.responseText);
          errorMessage += `: ${response.message || response.code}`;
        } catch (e) {
          errorMessage += `,状态码: ${xhr.status}`;
        }
        alert(errorMessage);
      }
    });
  });
</script>

四、总结 


本次讲述的项目主要是围绕用户的登录注册,增删改查等功能展开实现的。以上面三个功能为例,其他功能实现的流程类似于上述三个功能。以上就是本次讲述的项目的功能以及部分重要代码,如有问题,欢迎大家评论区留言或者私信作者!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值