完成用户模块后端接口

This commit is contained in:
lulz1 2024-10-25 16:51:47 +08:00
parent 068e8b9a0f
commit a8a7fadcb1
31 changed files with 1178 additions and 109 deletions

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
*.exe
/storage
/deploy
/.idea

View File

@ -0,0 +1,52 @@
package auth
import (
"bbs/app/provider/user"
"github.com/Superdanda/hade/framework/contract"
"github.com/Superdanda/hade/framework/gin"
"net/http"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
envService := c.MustMake(contract.EnvKey).(contract.Env)
userService := c.MustMake(user.UserKey).(user.Service)
// 如果在调试模式下根据参数的user_id 获取信息
if envService.AppEnv() == contract.EnvDevelopment {
userID, exist := c.DefaultQueryInt64("user_id", 0)
if exist {
authUser, _ := userService.GetUser(c, userID)
if authUser != nil {
c.Set("auth_user", authUser)
c.Next()
return
}
}
}
token, err := c.Cookie("hade_bbs")
if err != nil || token == "" {
c.ISetStatus(http.StatusUnauthorized).IText("请登录后操作")
return
}
authUser, err := userService.VerifyLogin(c, token)
if err != nil || authUser == nil {
c.ISetStatus(http.StatusUnauthorized).IText("请登录后操作")
return
}
c.Set("auth_user", authUser)
c.Next()
}
}
// GetAuthUser 获取已经验证的用户
func GetAuthUser(c *gin.Context) *user.User {
t, exist := c.Get("auth_user")
if !exist {
return nil
}
return t.(*user.User)
}

View File

@ -0,0 +1,13 @@
package response
import (
"fmt"
"github.com/Superdanda/hade/framework/gin"
)
func ResponseHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
fmt.Println("response handler")
}
}

View File

@ -0,0 +1,34 @@
package user
import (
"bbs/app/http/middleware/auth"
"bbs/app/http/middleware/response"
"bbs/app/provider/user"
"github.com/Superdanda/hade/framework/gin"
)
type UserApi struct{}
// RegisterRoutes 注册路由
func RegisterRoutes(r *gin.Engine) error {
api := &UserApi{}
if !r.IsBind(user.UserKey) {
r.Bind(&user.UserProvider{})
}
userGroup := r.Group("/user")
userGroup.Use(response.ResponseHandler())
{
// 登录
userGroup.POST("/login", api.Login)
// 登出
userGroup.GET("/logout", auth.AuthMiddleware(), api.Logout)
// 注册
userGroup.POST("/register", api.Register)
// 注册验证
userGroup.GET("/register/verify", api.Verify)
}
return nil
}

View File

@ -0,0 +1,45 @@
package user
import (
"bbs/app/provider/user"
"github.com/Superdanda/hade/framework/gin"
"net/http"
)
type loginParam struct {
UserName string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,gte=6"`
}
// Login 代表登录
// @Summary 用户登录
// @accept json
// @produce json
// @Tags user
// @Param loginParam body loginParam true "login with param"
// @Success 200 string Token "token"
// @Router /user/login [post]
func (api *UserApi) Login(c *gin.Context) {
userService := c.MustMake(user.UserKey).(user.Service)
param := &loginParam{}
if err := c.ShouldBind(param); err != nil {
c.ISetStatus(http.StatusBadRequest).IText("参数错误")
return
}
// 登录
model := &user.User{
UserName: param.UserName,
Password: param.Password,
}
userWithToken, err := userService.Login(c, model)
if err != nil {
c.ISetStatus(http.StatusInternalServerError).IText(err.Error())
return
}
// 输出
c.ISetOkStatus().IText(userWithToken.Token)
return
}

View File

@ -0,0 +1,32 @@
package user
import (
"bbs/app/http/middleware/auth"
"bbs/app/provider/user"
"github.com/Superdanda/hade/framework/gin"
)
// Logout 代表登出
// @Summary 用户登出
// @Description 调用表示用户登出
// @Accept json
// @Produce json
// @Tags user
// @Success 200 {string} Message "用户登出成功"
// @Router /user/logout [get]
func (api *UserApi) Logout(c *gin.Context) {
authUser := auth.GetAuthUser(c)
if authUser == nil {
c.ISetStatus(500).IText("用户未登录")
return
}
userService := c.MustMake(user.UserKey).(user.Service)
if err := userService.Logout(c, authUser); err != nil {
c.ISetStatus(500).IText(err.Error())
return
}
//c.ISetOkStatus().IText("用户登出成功")
c.IRedirect("/#/login")
return
}

View File

@ -0,0 +1,66 @@
package user
import (
"bbs/app/provider/user"
"fmt"
"github.com/Superdanda/hade/framework/contract"
"github.com/Superdanda/hade/framework/gin"
"time"
)
type registerParam struct {
UserName string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,gte=6"`
Email string `json:"email" binding:"required,gte=6"`
}
// Register 注册
// @Summary 用户注册
// @Description 用户注册接口
// @Accept json
// @Produce json
// @Tags user
// @Param registerParam body registerParam true "注册参数"
// @Success 200 string Message "注册成功"
// @Router /user/register [post]
func (api *UserApi) Register(c *gin.Context) {
// 验证参数
userService := c.MustMake(user.UserKey).(user.Service)
logger := c.MustMake(contract.LogKey).(contract.Log)
param := &registerParam{}
if err := c.ShouldBind(param); err != nil {
c.ISetStatus(400).IText("参数错误 ")
return
}
// 登录
model := &user.User{
UserName: param.UserName,
Password: param.Password,
Email: param.Email,
CreatedAt: time.Now(),
}
// 注册
userWithToken, err := userService.Register(c, model)
if err != nil {
logger.Error(c, err.Error(), map[string]interface{}{
"stack": fmt.Sprintf("%+v", err),
})
c.ISetStatus(500).IText(err.Error())
return
}
if userWithToken == nil {
c.ISetStatus(500).IText("注册失败")
return
}
if err := userService.SendRegisterMail(c, userWithToken); err != nil {
c.ISetStatus(500).IText("发送电子邮件失败")
return
}
c.ISetOkStatus().IText("注册成功,请前往邮箱查看邮件")
return
}

View File

@ -0,0 +1,39 @@
package user
import (
"bbs/app/provider/user"
"github.com/Superdanda/hade/framework/gin"
)
// Verify 代表验证注册信息
// @Summary 验证注册信息
// @Description 使用token验证用户注册信息
// @Accept json
// @Produce json
// @Tags user
// @Param token query string true "注册token"
// @Success 200 {string} Message "注册成功,请进入登录页面"
// @Router /user/register/verify [get]
func (api *UserApi) Verify(c *gin.Context) {
// 验证参数
userService := c.MustMake(user.UserKey).(user.Service)
token := c.Query("token")
if token == "" {
c.ISetStatus(400).IText("参数错误")
return
}
verified, err := userService.VerifyRegister(c, token)
if err != nil {
c.ISetStatus(500).IText(err.Error())
return
}
if !verified {
c.ISetStatus(500).IText("验证错误")
return
}
// 输出
c.IRedirect("/#/login").IText("注册成功,请进入登录页面")
}

View File

@ -0,0 +1,10 @@
package user
import "time"
// UserDTO 表示输出到外部的用户信息
type UserDTO struct {
ID int64 `json:"id,omitempty"`
UserName string `json:"user_name,omitempty"`
CreatedAt time.Time `json:"created_at"`
}

View File

@ -0,0 +1,15 @@
package user
import "bbs/app/provider/user"
// ConvertUserToDTO 将user转换为UserDTO
func ConvertUserToDTO(user *user.User) *UserDTO {
if user == nil {
return nil
}
return &UserDTO{
ID: user.ID,
UserName: user.UserName,
CreatedAt: user.CreatedAt,
}
}

View File

@ -2,6 +2,7 @@ package http
import (
"bbs/app/http/module/demo"
"bbs/app/http/module/user"
"github.com/Superdanda/hade/framework/contract"
"github.com/Superdanda/hade/framework/gin"
ginSwagger "github.com/Superdanda/hade/framework/middleware/gin-swagger"
@ -22,6 +23,9 @@ func Routes(core *gin.Engine) {
core.Use(static.Serve("/", static.LocalFile("./dist", false)))
err := demo.Register(core)
err = user.RegisterRoutes(core)
if err != nil {
return
}

View File

@ -70,6 +70,128 @@ const docTemplate = `{
}
}
}
},
"/user/login": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "用户登录",
"parameters": [
{
"description": "login with param",
"name": "loginParam",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user.loginParam"
}
}
],
"responses": {
"200": {
"description": "token",
"schema": {
"type": "string"
}
}
}
}
},
"/user/logout": {
"get": {
"description": "调用表示用户登出",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "用户登出",
"responses": {
"200": {
"description": "用户登出成功",
"schema": {
"type": "string"
}
}
}
}
},
"/user/register": {
"post": {
"description": "用户注册接口",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "用户注册",
"parameters": [
{
"description": "注册参数",
"name": "registerParam",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user.registerParam"
}
}
],
"responses": {
"200": {
"description": "注册成功",
"schema": {
"type": "string"
}
}
}
}
},
"/user/register/verify": {
"get": {
"description": "使用token验证用户注册信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "验证注册信息",
"parameters": [
{
"type": "string",
"description": "注册token",
"name": "token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "注册成功,请进入登录页面",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
@ -83,6 +205,43 @@ const docTemplate = `{
"type": "string"
}
}
},
"user.loginParam": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"password": {
"type": "string",
"minLength": 6
},
"username": {
"type": "string"
}
}
},
"user.registerParam": {
"type": "object",
"required": [
"email",
"password",
"username"
],
"properties": {
"email": {
"type": "string",
"minLength": 6
},
"password": {
"type": "string",
"minLength": 6
},
"username": {
"type": "string"
}
}
}
},
"securityDefinitions": {

View File

@ -63,6 +63,128 @@
}
}
}
},
"/user/login": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "用户登录",
"parameters": [
{
"description": "login with param",
"name": "loginParam",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user.loginParam"
}
}
],
"responses": {
"200": {
"description": "token",
"schema": {
"type": "string"
}
}
}
}
},
"/user/logout": {
"get": {
"description": "调用表示用户登出",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "用户登出",
"responses": {
"200": {
"description": "用户登出成功",
"schema": {
"type": "string"
}
}
}
}
},
"/user/register": {
"post": {
"description": "用户注册接口",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "用户注册",
"parameters": [
{
"description": "注册参数",
"name": "registerParam",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user.registerParam"
}
}
],
"responses": {
"200": {
"description": "注册成功",
"schema": {
"type": "string"
}
}
}
}
},
"/user/register/verify": {
"get": {
"description": "使用token验证用户注册信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "验证注册信息",
"parameters": [
{
"type": "string",
"description": "注册token",
"name": "token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "注册成功,请进入登录页面",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
@ -76,6 +198,43 @@
"type": "string"
}
}
},
"user.loginParam": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"password": {
"type": "string",
"minLength": 6
},
"username": {
"type": "string"
}
}
},
"user.registerParam": {
"type": "object",
"required": [
"email",
"password",
"username"
],
"properties": {
"email": {
"type": "string",
"minLength": 6
},
"password": {
"type": "string",
"minLength": 6
},
"username": {
"type": "string"
}
}
}
},
"securityDefinitions": {

View File

@ -7,6 +7,32 @@ definitions:
name:
type: string
type: object
user.loginParam:
properties:
password:
minLength: 6
type: string
username:
type: string
required:
- password
- username
type: object
user.registerParam:
properties:
email:
minLength: 6
type: string
password:
minLength: 6
type: string
username:
type: string
required:
- email
- password
- username
type: object
info:
contact:
email: yejianfeng
@ -50,6 +76,85 @@ paths:
summary: 获取所有学生
tags:
- demo
/user/login:
post:
consumes:
- application/json
parameters:
- description: login with param
in: body
name: loginParam
required: true
schema:
$ref: '#/definitions/user.loginParam'
produces:
- application/json
responses:
"200":
description: token
schema:
type: string
summary: 用户登录
tags:
- user
/user/logout:
get:
consumes:
- application/json
description: 调用表示用户登出
produces:
- application/json
responses:
"200":
description: 用户登出成功
schema:
type: string
summary: 用户登出
tags:
- user
/user/register:
post:
consumes:
- application/json
description: 用户注册接口
parameters:
- description: 注册参数
in: body
name: registerParam
required: true
schema:
$ref: '#/definitions/user.registerParam'
produces:
- application/json
responses:
"200":
description: 注册成功
schema:
type: string
summary: 用户注册
tags:
- user
/user/register/verify:
get:
consumes:
- application/json
description: 使用token验证用户注册信息
parameters:
- description: 注册token
in: query
name: token
required: true
type: string
produces:
- application/json
responses:
"200":
description: 注册成功,请进入登录页面
schema:
type: string
summary: 验证注册信息
tags:
- user
securityDefinitions:
ApiKeyAuth:
in: header

View File

@ -1,8 +0,0 @@
package user
const UserKey = "user"
type Service interface {
// 请在这里定义你的方法
Foo() string
}

View File

@ -1,31 +0,0 @@
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
}

View File

@ -1,16 +0,0 @@
package user
import "github.com/Superdanda/hade/framework"
type UserService struct {
container framework.Container
}
func NewUserService(params ...interface{}) (interface{}, error) {
container := params[0].(framework.Container)
return &UserService{container: container}, nil
}
func (s *UserService) Foo() string {
return ""
}

View File

@ -15,4 +15,12 @@ dev: # 调试模式
port: 8890 # 后端监听端口默认8072
monitor_folder: "" # 监听文件夹地址为空或者不填默认为AppFolder
frontend: # 前端调试模式配置
port: 8889 # 前端监听端口, 默认8071
port: 8889 # 前端监听端口, 默认8071
smtp:
host: "smtp.126.com"
port: 25
from: "superdanda@126.com"
username: "superdanda"
password: "A10337191315."

View File

@ -1,23 +1,22 @@
conn_max_idle: 10 # 通用配置,连接池最大空闲连接数
conn_max_open: 100 # 通用配置,连接池最大连接数
conn_max_lifetime: 1h # 通用配置,连接数最大生命周期
protocol: tcp # 通用配置,传输协议
loc: Local # 通用配置,时区
default:
driver: mysql # 连接驱动
dsn: "" # dsn如果设置了dsn, 以下的所有设置都不生效
host: 47.97.21.20 # ip地址
port: 3306 # 端口
database: questionnaire # 数据库
username: root # 用户名
password: "kerowsqw34" # 密码
charset: utf8mb4 # 字符集
collation: utf8mb4_unicode_ci # 字符序
timeout: 10s # 连接超时
read_timeout: 2s # 读超时
write_timeout: 2s # 写超时
parse_time: true # 是否解析时间
protocol: tcp # 传输协议
loc: Local # 时区
driver: mysql # 连接驱动
dsn: "" # dsn如果设置了dsn, 以下的所有设置都不生效
host: 47.97.21.20 # ip地址
port: 3306 # 端口
database: questionnaire # 数据库
username: root # 用户名
password: "kerowsqw34" # 密码
charset: utf8mb4 # 字符集
collation: utf8mb4_unicode_ci # 字符序
timeout: 10s # 连接超时
read_timeout: 2s # 读超时
write_timeout: 2s # 写超时
parse_time: true # 是否解析时间
protocol: tcp # 传输协议
loc: Local # 时区

8
go.mod
View File

@ -3,8 +3,9 @@ module bbs
go 1.23.2
require (
github.com/Superdanda/hade v1.0.2
github.com/Superdanda/hade v1.0.4
github.com/swaggo/swag v1.16.4
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)
require (
@ -21,7 +22,7 @@ require (
github.com/robfig/cron/v3 v3.0.0 // indirect
github.com/sevlyar/go-daemon v0.1.6 // indirect
github.com/spf13/cast v1.7.0 // indirect
gorm.io/gorm v1.25.12 // indirect
gorm.io/gorm v1.25.12
)
require (
@ -87,6 +88,7 @@ require (
golang.org/x/sync v0.8.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/clickhouse v0.6.1 // indirect
@ -126,7 +128,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/crypto v0.28.0
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
)

8
go.sum
View File

@ -36,8 +36,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/Superdanda/hade v1.0.2 h1:AxlYNeUVO90j+LUHiZW6rg36DbGIXi6gUopURGVMzio=
github.com/Superdanda/hade v1.0.2/go.mod h1:z52uXdtEfX25FRCj7YeQOctw6fho9nIaIWc/picNhuA=
github.com/Superdanda/hade v1.0.4 h1:NFsH8BBbsHmbedkGHdpjGk3e49GEJtRWU4ENOxp0sfU=
github.com/Superdanda/hade v1.0.4/go.mod h1:z52uXdtEfX25FRCj7YeQOctw6fho9nIaIWc/picNhuA=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@ -443,11 +443,15 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -9,7 +9,12 @@
},
"dependencies": {
"element-ui": "^2.3.4",
"vue": "^2.5.16"
"vue": "^2.5.16",
"vue-router": "^3.5.3",
"vue-style-loader": "^4.1.3",
"vuex": "^3.6.2",
"axios": "^0.24.0",
"js-cookie": "^3.0.1"
},
"engines": {
"node": ">=6"

View File

@ -1,23 +1,15 @@
<template>
<div id="app">
<img src="./assets/logo.png">
<div>
<el-button @click="startHacking">Start</el-button>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
components: {
},
methods: {
startHacking () {
this.$notify({
title: 'It works! again',
type: 'success',
message: 'We\'ve laid the ground work for you. It\'s time for you to build something epic!',
duration: 5000
})
}
}
}
</script>

View File

@ -2,10 +2,14 @@ import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
import store from './store'
import router from './router/index.js'
Vue.use(ElementUI)
new Vue({
el: '#app',
router: router,
store: store,
render: h => h(App)
})

33
src/views/404.vue Normal file
View File

@ -0,0 +1,33 @@
<script>
export default {
name: "notfound",
data() {
return {
model: {
username: "",
password: ""
},
loading: false,
};
},
methods: {}
};
</script>
<template>
<div class="notfound">
<el-card>
<h2>页面找不到了</h2>
</el-card>
</div>
</template>
<style scoped>
.notfound {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
margin-top: 240px;
}
</style>

View File

@ -0,0 +1,25 @@
<script>
import BBSHeader from './header'
export default {
components: {
'bbs-header': BBSHeader
},
methods: {}
}
</script>
<template>
<el-container>
<el-header>
<bbs-header></bbs-header>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</template>
<style>
</style>

View File

@ -0,0 +1,65 @@
<script>
export default {
methods: {}
}
</script>
<template>
<el-row type="flex" justify="center" align="middle">
<el-col :span="8">
<el-row>
<el-col :span="8">
<div class="hade_title"><a href="/" id="home-link" title="hadecast">hadecast</a></div>
</el-col>
<el-col class="" :span="8" :offset="8">
<div class="header_name">
<router-link class="to-link" :to="{path: '/create'}"><a title="hadecast">我要提问</a></router-link>
| jianfengye | <a href="/user/logout" title="hadecast">登出</a></div>
</el-col>
</el-row>
</el-col>
</el-row>
</template>
<style scoped>
.hade_title {
float: left;
vertical-align: top;
font-size: 24px;
font-weight: 400;
margin: 0 15px 0 0;
line-height: 34px;
padding-top: 10px;
}
.header_name {
float: right;
font-size: 13px;
font-weight: 400;
margin: 0 15px 0 0;
line-height: 34px;
padding-top: 10px;
padding-top: 10px;
background-color: transparent;
color: #486e9b;
text-decoration: none;
}
.header_name > a {
background-color: transparent;
color: #486e9b;
text-decoration: none;
}
.hade_title > a {
background-color: transparent;
color: #486e9b;
text-decoration: none;
}
.el-header {
background-color: #409EFF;
text-align: center;
line-height: 80px;
}
</style>

94
src/views/login/index.vue Normal file
View File

@ -0,0 +1,94 @@
<script>
export default {
name: "login",
data() {
return {
model: {
username: "",
password: ""
},
};
},
methods: {
handleLogin() {
this.$store.dispatch('user/login', this.model).then(() => {
this.$router.push({path: this.redirect || '/'})
}).catch(() => {
})
}
}
};
</script>
<template>
<div class="login">
<el-card>
<h2>登录</h2>
<el-form
class="login-form"
>
<el-form-item prop="username">
<el-input v-model="model.username" placeholder="用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
placeholder="密码"
type="password"
v-model="model.password"
></el-input>
</el-form-item>
<el-row>
<el-col class="register">
还没有账号请点击
<router-link class="to-link" :to="{path: '/register'}">
<el-link type="primary">注册</el-link>
</router-link>
</el-col>
</el-row>
<el-form-item>
<el-button
class="login-button"
type="primary"
native-type="submit"
@click="handleLogin"
block
>登录
</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<style scoped>
.to-link {
text-decoration: none;
}
.register {
font-weight: 500;
font-size: 14px;
}
.login {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.login-button {
width: 100%;
margin-top: 40px;
}
.login-form {
width: 390px;
}
.forgot-password {
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,100 @@
<script setup>
import request from '../../utils/request'
export default {
name: "register",
data() {
return {
form: {
username: '',
password: '',
email: '',
repassword: ''
},
loading: false,
};
},
methods: {
submitForm: function(e) {
if (this.form.repassword !== this.form.password) {
this.$message.error("两次输入密码不一致");
return;
}
const that = this;
request({
url: '/user/register',
method: 'post',
data: this.form
}).then(function (response) {
debugger
const msg = response.data
that.$message.success(msg);
})
}
}
};
</script>
<template>
<div class="register">
<el-card>
<h2>注册</h2>
<el-form v-model="form" class="register-form">
<el-form-item >
<el-input v-model="form.username" placeholder="用户名" ></el-input>
</el-form-item>
<el-form-item >
<el-input v-model="form.email" placeholder="邮箱"></el-input>
</el-form-item>
<el-form-item >
<el-input
placeholder="密码"
type="password"
v-model="form.password"
></el-input>
</el-form-item>
<el-form-item >
<el-input
placeholder="确认密码"
type="password"
v-model="form.repassword"
></el-input>
</el-form-item>
<el-form-item>
<el-button
:loading="loading"
class="login-button"
type="primary"
native-type="submit"
@click="submitForm"
block
>注册</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<style scoped>
.register {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.login-button {
width: 100%;
margin-top: 40px;
}
.register-form {
width: 390px;
}
.forgot-password {
margin-top: 10px;
}
.send_verify_code{
}
</style>

View File

@ -50,16 +50,16 @@ module.exports = (options = {}) => ({
],
resolve: {
alias: {
'~': resolve(__dirname, 'src')
'@': resolve(__dirname, 'src')
},
extensions: ['.js', '.vue', '.json', '.css']
},
devServer: {
host: '127.0.0.1',
port: 8010,
port: 8890,
proxy: {
'/api/': {
target: 'http://127.0.0.1:8080',
target: 'http://127.0.0.1:8890',
changeOrigin: true,
pathRewrite: {
'^/api': ''

View File

@ -170,6 +170,13 @@ autoprefixer@^6.3.1, autoprefixer@^6.6.0:
postcss "^5.2.16"
postcss-value-parser "^3.2.3"
axios@^0.24.0:
version "0.24.0"
resolved "https://registry.npmmirror.com/axios/-/axios-0.24.0.tgz"
integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
dependencies:
follow-redirects "^1.14.4"
babel-code-frame@^6.11.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "http://registry.npm.taobao.org/babel-code-frame/download/babel-code-frame-6.22.0.tgz"
@ -1742,6 +1749,11 @@ flatten@^1.0.2:
resolved "http://registry.npm.taobao.org/flatten/download/flatten-1.0.2.tgz"
integrity sha512-6u/bzbUK+6iOENlqGFkl94EqdAL/FVRhxMWbAE0OBmRsBl64BESxvVRD3CWdilAeka/3WlEZP+0MrKvtYpYFQQ==
follow-redirects@^1.14.4:
version "1.15.9"
resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
for-in@^1.0.1:
version "1.0.2"
resolved "http://registry.npm.taobao.org/for-in/download/for-in-1.0.2.tgz"
@ -2207,6 +2219,11 @@ js-base64@^2.1.9:
resolved "http://registry.npm.taobao.org/js-base64/download/js-base64-2.1.9.tgz"
integrity sha512-f+5mYh8iF7FlF7zgmj/yqvvYQUHI0kAxGiLjIfNxZzqJ7RQNc4sjgp8crVJw0Kzv2O6aFGZWgMTnO71I9utHSg==
js-cookie@^3.0.1:
version "3.0.5"
resolved "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
js-tokens@^3.0.0:
version "3.0.1"
resolved "http://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.1.tgz"
@ -2310,16 +2327,7 @@ loader-utils@^0.2.16:
json5 "^0.5.0"
object-assign "^4.0.1"
loader-utils@^1.0.2:
version "1.1.0"
resolved "http://registry.npm.taobao.org/loader-utils/download/loader-utils-1.1.0.tgz"
integrity sha512-gkD9aSEG9UGglyPcDJqY9YBTUtCLKaBK6ihD2VP1d1X60lTfFspNZNulGBBbUZLkPygy4LySYHyxBpq+VhjObQ==
dependencies:
big.js "^3.1.3"
emojis-list "^2.0.0"
json5 "^0.5.0"
loader-utils@^1.1.0:
loader-utils@^1.0.2, loader-utils@^1.1.0:
version "1.1.0"
resolved "http://registry.npm.taobao.org/loader-utils/download/loader-utils-1.1.0.tgz"
integrity sha512-gkD9aSEG9UGglyPcDJqY9YBTUtCLKaBK6ihD2VP1d1X60lTfFspNZNulGBBbUZLkPygy4LySYHyxBpq+VhjObQ==
@ -4000,10 +4008,23 @@ vue-loader@^13.3.0:
vue-style-loader "^3.0.0"
vue-template-es2015-compiler "^1.6.0"
vue-router@^3.5.3:
version "3.6.5"
resolved "https://registry.npmmirror.com/vue-router/-/vue-router-3.6.5.tgz"
integrity sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==
vue-style-loader@^3.0.0:
version "3.0.1"
resolved "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-3.0.1.tgz"
integrity sha512-SjeCPpS6yWZwBOXsjayw8F2rqtFmjgmRsSMJQOTBZmaJVt1c+vLN6dBppeiIiQ4BGahYGiBvLfbCBoiKMoxJAQ==
version "3.1.2"
resolved "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-3.1.2.tgz"
integrity sha512-ICtVdK/p+qXWpdSs2alWtsXt9YnDoYjQe0w5616j9+/EhjoxZkbun34uWgsMFnC1MhrMMwaWiImz3K2jK1Yp2Q==
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-style-loader@^4.1.3:
version "4.1.3"
resolved "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz"
integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
@ -4021,11 +4042,16 @@ vue-template-es2015-compiler@^1.6.0:
resolved "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz"
integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==
vue@^2.5.16, vue@^2.5.2:
vue@^2.0.0, vue@^2.5.16, vue@^2.5.2:
version "2.5.16"
resolved "https://registry.npmmirror.com/vue/-/vue-2.5.16.tgz"
integrity sha512-/ffmsiVuPC8PsWcFkZngdpas19ABm5mh2wA7iDqcltyCTwlgZjHGeJYOXkBMo422iPwIcviOtrTCUpSfXmToLQ==
vuex@^3.6.2:
version "3.6.2"
resolved "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz"
integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
watchpack@^1.3.1:
version "1.3.1"
resolved "http://registry.npm.taobao.org/watchpack/download/watchpack-1.3.1.tgz"