完成用户模块后端接口
This commit is contained in:
parent
a8a7fadcb1
commit
40bf977f5b
|
@ -0,0 +1,52 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
const UserKey = "user"
|
||||
|
||||
type Service interface {
|
||||
// Register 注册用户,注意这里只是将用户注册, 并没有激活, 需要调用
|
||||
// 参数:user必填,username,password, email
|
||||
// 返回值: user 带上token
|
||||
Register(ctx context.Context, user *User) (*User, error)
|
||||
// SendRegisterMail 发送注册的邮件
|
||||
// 参数:user必填: username, password, email, token
|
||||
SendRegisterMail(ctx context.Context, user *User) error
|
||||
// VerifyRegister 注册用户,验证注册信息, 返回验证是否成功
|
||||
VerifyRegister(ctx context.Context, token string) (bool, error)
|
||||
|
||||
// Login 登录相关,使用用户名密码登录,获取完成User信息
|
||||
Login(ctx context.Context, user *User) (*User, error)
|
||||
// Logout 登出
|
||||
Logout(ctx context.Context, user *User) error
|
||||
// VerifyLogin 登录验证
|
||||
VerifyLogin(ctx context.Context, token string) (*User, error)
|
||||
|
||||
// GetUser 获取用户信息
|
||||
GetUser(ctx context.Context, userID int64) (*User, error)
|
||||
}
|
||||
|
||||
// User 代表一个用户,注意这里的用户信息字段在不同接口和参数可能为空
|
||||
type User struct {
|
||||
ID int64 `gorm:"column:id;primary_key;auto_increment" json:"id"` // 代表用户id, 只有注册成功之后才有这个id,唯一表示一个用户
|
||||
UserName string `gorm:"column:username;type:varchar(255);not null" json:"username"`
|
||||
Password string `gorm:"column:password;type:varchar(255);not null" json:"password"`
|
||||
Email string `gorm:"column:email;type:varchar(255);not null" json:"email"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null;<-:create" json:"createdAt"`
|
||||
|
||||
Token string `gorm:"-"` // token 可以用作注册token或者登录token
|
||||
}
|
||||
|
||||
// MarshalBinary 实现BinaryMarshaler 接口
|
||||
func (b *User) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(b)
|
||||
}
|
||||
|
||||
// UnmarshalBinary 实现 BinaryUnMarshaler 接口
|
||||
func (b *User) UnmarshalBinary(bt []byte) error {
|
||||
return json.Unmarshal(bt, b)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"github.com/Superdanda/hade/framework"
|
||||
)
|
||||
|
||||
type UserProvider struct {
|
||||
framework.ServiceProvider
|
||||
|
||||
c framework.Container
|
||||
}
|
||||
|
||||
func (sp *UserProvider) Name() string {
|
||||
return UserKey
|
||||
}
|
||||
|
||||
func (sp *UserProvider) Register(c framework.Container) framework.NewInstance {
|
||||
return NewUserService
|
||||
}
|
||||
|
||||
func (sp *UserProvider) IsDefer() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sp *UserProvider) Params(c framework.Container) []interface{} {
|
||||
return []interface{}{c}
|
||||
}
|
||||
|
||||
func (sp *UserProvider) Boot(c framework.Container) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/Superdanda/hade/framework"
|
||||
"github.com/Superdanda/hade/framework/contract"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/gomail.v2"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
container framework.Container
|
||||
logger contract.Log
|
||||
config contract.Config
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
// 生成指定长度的随机令牌
|
||||
func genToken(n int) (string, error) {
|
||||
b := make([]byte, n) // 创建存储随机字节的切片
|
||||
letterLen := byte(len(letterBytes)) // 预存字母表长度,避免重复计算
|
||||
|
||||
// 遍历生成随机字节
|
||||
for i := range b {
|
||||
randomByte := make([]byte, 1) // 每次生成一个随机字节
|
||||
if _, err := rand.Read(randomByte); err != nil {
|
||||
return "", err // 出现错误时返回错误
|
||||
}
|
||||
b[i] = letterBytes[randomByte[0]%letterLen] // 将随机字节映射为字母表中的字符
|
||||
}
|
||||
|
||||
return string(b), nil // 返回生成的令牌
|
||||
}
|
||||
|
||||
func (u *UserService) Register(ctx context.Context, user *User) (*User, error) {
|
||||
ormService := u.container.MustMake(contract.ORMKey).(contract.ORMService)
|
||||
|
||||
//验证用户是否已经存在
|
||||
_, err, _ := u.checkUserNameOrEmailIfExist(ormService, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//创建一个令牌
|
||||
token, err := genToken(10)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.Token = token
|
||||
// 将请求注册进入redis,保存一天
|
||||
cacheService := u.container.MustMake(contract.CacheKey).(contract.CacheService)
|
||||
key := fmt.Sprintf("user:register:%v", user.Token)
|
||||
if err := cacheService.SetObj(ctx, key, user, 24*time.Hour); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (u *UserService) SendRegisterMail(ctx context.Context, user *User) error {
|
||||
logger := u.container.MustMake(contract.LogKey).(contract.Log)
|
||||
configer := u.container.MustMake(contract.ConfigKey).(contract.Config)
|
||||
|
||||
// 配置服务中获取发送邮件需要的参数
|
||||
host := configer.GetString("app.smtp.host")
|
||||
port := configer.GetInt("app.smtp.port")
|
||||
username := configer.GetString("app.smtp.username")
|
||||
password := configer.GetString("app.smtp.password")
|
||||
from := configer.GetString("app.smtp.from")
|
||||
domain := configer.GetString("app.domain")
|
||||
|
||||
// 实例化gomail
|
||||
d := gomail.NewDialer(host, port, username, password)
|
||||
|
||||
// 组装message
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", from)
|
||||
m.SetAddressHeader("To", user.Email, user.UserName)
|
||||
m.SetHeader("Subject", "感谢您注册我们的网址")
|
||||
link := fmt.Sprintf("%v/user/register/verify?token=%v", domain, user.Token)
|
||||
m.SetBody("text/html", fmt.Sprintf("请点击下面的链接完成注册:%s", link))
|
||||
|
||||
// 发送电子邮件
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
logger.Error(ctx, "send email error", map[string]interface{}{
|
||||
"err": err,
|
||||
"message": m,
|
||||
})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserService) VerifyRegister(ctx context.Context, token string) (bool, error) {
|
||||
container := u.container
|
||||
cacheService := container.MustMake(contract.CacheKey).(contract.CacheService)
|
||||
|
||||
key := fmt.Sprintf("user:register:%v", token)
|
||||
user := &User{}
|
||||
if err := cacheService.GetObj(ctx, key, user); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if user.Token != token {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
//验证邮箱,用户名的唯一
|
||||
ormService := u.container.MustMake(contract.ORMKey).(contract.ORMService)
|
||||
|
||||
//验证用户是否已经存在
|
||||
_, err, _ := u.checkUserNameOrEmailIfExist(ormService, user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
//如果没有重复,将用户数据保存到数据库
|
||||
// 验证成功将密码存储数据库之前需要加密,不能原文存储进入数据库
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
user.Password = string(hash)
|
||||
// 具体在数据库创建用户
|
||||
db, err := ormService.GetDB()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := db.Create(user).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (u *UserService) Login(ctx context.Context, user *User) (*User, error) {
|
||||
ormService := u.container.MustMake(contract.ORMKey).(contract.ORMService)
|
||||
db, err := ormService.GetDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userDB := &User{}
|
||||
if err := db.Where("username=?", user.UserName).First(userDB).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.Wrap(err, "该用户未注册")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(userDB.Password), []byte(user.Password)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userDB.Password = ""
|
||||
// 缓存session
|
||||
cacheService := u.container.MustMake(contract.CacheKey).(contract.CacheService)
|
||||
token, err := genToken(10)
|
||||
key := fmt.Sprintf("session:%v", token)
|
||||
if err := cacheService.SetObj(ctx, key, userDB, 24*time.Hour); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userDB.Token = token
|
||||
return userDB, nil
|
||||
}
|
||||
|
||||
func (u *UserService) Logout(ctx context.Context, user *User) error {
|
||||
cacheService := u.container.MustMake(contract.CacheKey).(contract.CacheService)
|
||||
userSession, err := u.VerifyLogin(ctx, user.Token)
|
||||
// 不需要做任何操作
|
||||
if err != nil || userSession.UserName != user.UserName {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("session:%v", user.Token)
|
||||
_ = cacheService.Del(ctx, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserService) VerifyLogin(ctx context.Context, token string) (*User, error) {
|
||||
if token == "" {
|
||||
return nil, errors.New("token不能为空")
|
||||
}
|
||||
cacheService := u.container.MustMake(contract.CacheKey).(contract.CacheService)
|
||||
key := fmt.Sprintf("session:%v", token)
|
||||
user := &User{}
|
||||
if err := cacheService.GetObj(ctx, key, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (u *UserService) GetUser(ctx context.Context, userID int64) (*User, error) {
|
||||
ormService := u.container.MustMake(contract.ORMKey).(contract.ORMService)
|
||||
db, err := ormService.GetDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := &User{ID: userID}
|
||||
if err := db.WithContext(ctx).First(user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func NewUserService(params ...interface{}) (interface{}, error) {
|
||||
container := params[0].(framework.Container)
|
||||
logger := container.MustMake(contract.LogKey).(contract.Log)
|
||||
config := container.MustMake(contract.ConfigKey).(contract.Config)
|
||||
return &UserService{container: container, logger: logger, config: config}, nil
|
||||
}
|
||||
|
||||
func (s *UserService) Foo() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *UserService) checkUserNameOrEmailIfExist(dbService contract.ORMService, user *User) (bool, error, *User) {
|
||||
db, err := dbService.GetDB()
|
||||
userDB := &User{}
|
||||
if err != nil {
|
||||
return false, err, nil
|
||||
}
|
||||
if !errors.Is(db.Where(&User{Email: user.Email}).First(userDB).Error, gorm.ErrRecordNotFound) {
|
||||
return true, errors.New("邮箱已注册用户,不能重复注册"), userDB
|
||||
}
|
||||
if !errors.Is(db.Where(&User{UserName: user.UserName}).First(userDB).Error, gorm.ErrRecordNotFound) {
|
||||
return true, errors.New("邮箱已注册用户,不能重复注册"), userDB
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,71 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
|
||||
import ViewLogin from '../views/login/index'
|
||||
import ViewRegister from '../views/register/index'
|
||||
import View404 from '../views/404'
|
||||
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
/**
|
||||
* constantRoutes
|
||||
* a base page that does not have permission requirements
|
||||
* all roles can be accessed
|
||||
*/
|
||||
export const constantRoutes = [
|
||||
{
|
||||
path: '/login',
|
||||
component: ViewLogin,
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: ViewRegister,
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: View404,
|
||||
hidden: true
|
||||
},
|
||||
// {
|
||||
// path: '/',
|
||||
// redirect: '/',
|
||||
// component: ViewContainer,
|
||||
// children: [
|
||||
// {
|
||||
// path: '',
|
||||
// component: ViewList
|
||||
// },
|
||||
// {
|
||||
// path: 'detail',
|
||||
// component: ViewDetail
|
||||
// },
|
||||
// {
|
||||
// path: 'create',
|
||||
// component: ViewCreate
|
||||
// },
|
||||
// {
|
||||
// path: 'edit',
|
||||
// component: ViewEdit
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// 404 page must be placed at the end !!!
|
||||
{ path: '*', redirect: '/404', hidden: true }
|
||||
]
|
||||
|
||||
const createRouter = () => new Router({
|
||||
routes: constantRoutes
|
||||
})
|
||||
|
||||
const router = createRouter()
|
||||
|
||||
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
|
||||
export function resetRouter() {
|
||||
const newRouter = createRouter()
|
||||
router.matcher = newRouter.matcher // reset router
|
||||
}
|
||||
|
||||
export default router
|
|
@ -0,0 +1,11 @@
|
|||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
size: state => state.app.size,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
name: state => state.user.name,
|
||||
visitedViews: state => state.tagsView.visitedViews,
|
||||
cachedViews: state => state.tagsView.cachedViews,
|
||||
}
|
||||
export default getters
|
|
@ -0,0 +1,25 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import getters from './getters'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
// https://webpack.js.org/guides/dependency-management/#requirecontext
|
||||
const modulesFiles = require.context('./modules', true, /\.js$/)
|
||||
|
||||
// you do not need `import app from './modules/app'`
|
||||
// it will auto require all vuex module from modules file
|
||||
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
|
||||
// set './app.js' => 'app'
|
||||
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
|
||||
const value = modulesFiles(modulePath)
|
||||
modules[moduleName] = value.default
|
||||
return modules
|
||||
}, {})
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules,
|
||||
getters
|
||||
})
|
||||
|
||||
export default store
|
|
@ -0,0 +1,75 @@
|
|||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import { resetRouter } from '@/router'
|
||||
import request from "../../utils/request";
|
||||
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
token: getToken(),
|
||||
name: 'jianfengye',
|
||||
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif'
|
||||
}
|
||||
}
|
||||
|
||||
const state = getDefaultState()
|
||||
|
||||
const mutations = {
|
||||
RESET_STATE: (state) => {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
// user login
|
||||
login({ commit }, userInfo) {
|
||||
const { username, password } = userInfo
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post("/user/login",{ username: username.trim(), password: password }).then(response => {
|
||||
const token = response.data
|
||||
commit('SET_TOKEN', token)
|
||||
setToken(token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// user logout
|
||||
logout({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request("/user/logout", state.token).then(() => {
|
||||
removeToken() // must remove token first
|
||||
resetRouter()
|
||||
commit('RESET_STATE')
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// remove token
|
||||
resetToken({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
removeToken() // must remove token first
|
||||
commit('RESET_STATE')
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'hade_bbs'
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
return Cookies.set(TokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
import axios from 'axios'
|
||||
import { Message } from 'element-ui'
|
||||
|
||||
|
||||
// 创建一个axios
|
||||
const service = axios.create({
|
||||
withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 10000 // request timeout
|
||||
})
|
||||
|
||||
// 请求的配置
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
// 如果request 有错误,打印信息
|
||||
console.log(error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// response中统一做处理
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
// Any status code that lie within the range of 2xx cause this function to trigger
|
||||
// Do something with response data
|
||||
// 判断http status是否为200
|
||||
if (response.status !== 200) {
|
||||
const data = response.data
|
||||
if (typeof data == 'string') {
|
||||
Message({
|
||||
message: data,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
}
|
||||
}
|
||||
return response
|
||||
},
|
||||
error => {
|
||||
// Any status codes that falls outside the range of 2xx cause this function to trigger
|
||||
// Do something with response error
|
||||
console.log('err: ' + error) // for debug
|
||||
// 打印Message消息
|
||||
Message({
|
||||
message: error.response.data,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
Loading…
Reference in New Issue