本篇使用Vue2开发前端,Go语言开发服务端,使用nginx代理部署实现登录页面及其校验功能。
目录
1.部署结构
2.Vue2前端
2.1代码结构
2.1源码
3.Go后台服务
3.2代码结构
3.2 源码
3.3单测效果
4.nginx
5.运行效果
6.问题总结
1.部署结构
2.Vue2前端
2.1代码结构
2.1源码
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>demo</title>
</head>
<body>
<div id="myapp"></div>
<!-- built files will be auto injected -->
</body>
</html>
src/App.vue
<template>
<div>
<router-view></router-view>
</div>
</template>
src/assets/css/reset.css
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
src/components/Home.vue
<template>
<div id="YYYYYYYYYYY" class="hello">
<button @click="goLogin">登录</button>
<el-button type="primary">你好</el-button>
<el-button type="info">你好</el-button>
<el-button type="danger">你好</el-button>
<el-button type="success">你好</el-button>
<el-tag> fsfsd dsd dsfv</el-tag>
<i class="fa fa-user"></i>
<i class="fa fa-users"></i>
</div>
</template><style lang="scss">
.hello {
background: yello;
.el-button {
color: red;
}
}
@import url('../assets/css/reset.css')
</style>
<script>
export default {
methods: {
goLogin () {
this.$router.push('/login')
}
}
}
</script>
src/components/Login.vue
<template>
<div class="login">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>业务后台管理系统</span>
</div><el-form label-width="100px" :model="form" ref="form" :rules='rules'>
<el-form-item label="用户名" prop='username'>
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop='password'>
<el-input type='password' v-model="form.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type='primary' @click="login('form')">登录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>/*
原生AJAX和Axios在使用上存在一定的区别。Axios可以支持多种方式,包括浏览器环境、node环境,而AJAX则只能在浏览器环境中使用。
Axios还支持多种请求方式,包括GET、POST、PUT、DELETE等;而AJAX只能支持GET和POST方式发送请求。此外,Axios还可以拦截请求和响应。
*/<script>
//登录验证的封装
import {nameRule, passRule} from '../utils/validate.js'import {setToken} from '@/utils/dealtoken.js'
export default {
data () {
return {
form: {
username: "",
password: ""
},
rules: {
username: [{validator: nameRule, required: true, trigger: "blur"}],
password: [{validator: passRule, required: true, trigger: "blur"}]
}
}
},
methods: {
login(form) {
this.$refs[form].validate((valid) => {
if (valid) {
console.log(this.form)
//this.$router.push('/home')
//解决axios post请求 404 OPTIONS问题
const posthead = {
headers: {
'Content-Type': 'text/plain;charset=utf-8',
},
//withCredentials: 'same-origin'
}
this.axios.post('http://localhost:8282/login', JSON.stringify(this.form), posthead).then(res => {
console.log(res)
if (res.status === 200) {
//localStorage.setItem('username', res.data.username)
setToken('username', res.data.username)
this.$message({message: res.data, type: 'success'})
this.$router.push('/home')
console.log("post mid")
}
})
} else {
console.error(this.form)
}
})
}
}
}
</script><style lang='scss'>
.login {
width: 100%;
height: 100%;
position: absolute;
background: #409EFF;
.box-card {
width: 450px;
margin: 200px auto;
.el-card_header {
font-size: 34px;
}
.el-button {
width: 100%;
}
}
}
</style>
src/main.js
import Vue from 'vue'
import App from './App'
import 'font-awesome/css/font-awesome.min.css'
import axios from 'axios'
import router from './router'// 挂载到原型就可以全局使用
Vue.prototype.axios = axiosimport ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
new Vue({
router,
render: h => h(App)
}).$mount('#myapp')
src/router/index.js
import Vue from 'vue'
import Home from '@/components/Home'
import VueRouter from 'vue-router'Vue.use(VueRouter)
const routes = [
{ path: '/', redirect: '/login', component: () => import('@/components/Login') },
{ path: '/login', name: 'Login', component: () => import('@/components/Login') },
{ path: '/home', component: Home },
{ path: '*', component: Home }
]
export default new VueRouter({
mode: 'history',
routes: routes
})
src/utils/validate.js
// Token的封装 Token存放在localStorage
export function setToken(tokenkey, token) {
return localStorage.setItem(tokenkey, token)
}export function getToken(tokenkey) {
return localStorage.getItem(tokenkey)
}export function removeToken(tokenkey) {
return localStorage.removeItem(tokenkey)
}
src/utils/dealtoken.js
//用户名匹配
export function nameRule (rule, value, callback) {
let reg = /(^[a-zA-Z0-9]{4,10}$)/;
if (value === "") {
callback(new Error("请输入用户名"));
} else if (!reg.test(value)) {
callback(new Error("请输入4-10用户名"));
} else {
callback();
}
}//密码匹配
export function passRule (rule, value, callback) {
let pass = /^\S*(?=\S{6,12})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/;
if (value === "") {
callback(new Error("请输入密码"));
} else if (!pass.test(value)) {
callback(new Error("请输入6-12位密码需要包含大小写和数字及特殊字符"));
} else {
callback();
}
}
plugins/element.js
import Vue from 'vue'
import { Button, Tag } from 'element-ui'Vue.use(Button)
Vue.use(Tag)
3.Go后台服务
3.2代码结构
3.2 源码
controller/login.go
package controller
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path/filepath""github.com/gin-gonic/gin"
)// post http://127.0.0.1:8181/login
// axios.post 和 post json处理
func LoginPost(ctx *gin.Context) {
version := ctx.DefaultQuery("version", "V1.0.0.1")//前端使用axios直接传递form时,axios会默认使用json,必须使用下面方式获取json数据,解析后再使用
data, _ := ioutil.ReadAll(ctx.Request.Body)
type UserInfo struct {
Username string
Password string
}
var u UserInfo
err := json.Unmarshal(data, &u)
if err != nil {
fmt.Println(err)
}
username := u.Username
password := u.Passwordfmt.Println("login info:: ", version, username, password)
/*
ctx.Header("Access-Control-Allow-Origin", "*")ctx.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
ctx.Header("Access-Control-Allow-Credentials", "true")
ctx.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
ctx.Header("content-type", "application/json;charset=UTF-8")ctx.Writer.Header().Set("Access-Control-Max-Age", "86400")
ctx.Writer.Header().Set("Access-Control-Allow-Methods", "*")
ctx.Writer.Header().Set("Access-Control-Allow-Headers", "*")
ctx.Writer.Header().Set("Access-Control-Expose-Headers", "*")
ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
*/if username == "123456" && password == "1234abcdE@" {
ctx.String(http.StatusOK, "登录成功")
} else {
ctx.String(http.StatusNotFound, "用户名或密码错误")
}
}// http://127.0.0.1:8181/formlogin
// form表单提交处理 application/x-www-form-urlencoded
func FormLoginPost(ctx *gin.Context) {//第一种
username := ctx.PostForm("username")
password := ctx.PostForm("password")//第二种
/*
username := ctx.DefaultPostForm("username", "somebody")
password := ctx.DefaultPostForm("password", "***")
*///第三种
/*
username, ok := ctx.GetPostForm("username")
if !ok {
username = "取不到的话"
}
password, ok := ctx.GetPostForm("password")
if !ok {
password = "***"
}
*/fmt.Println("FormLoginPost :: ", username, password)
/*
ctx.HTML(http.StatusOK, "home.html", gin.H{
"Name": username,
"Password": password,
})
*/
ctx.JSON(http.StatusOK, gin.H{
"Name": username,
"Password": password,
})if username == "123456" && password == "1234abcdE@" {
ctx.String(http.StatusOK, "登录成功")
} else {
ctx.String(http.StatusNotFound, "用户名或密码错误")
}
}// form表单提交文件上传处理 multipart/form-data
func UploadFile(ctx *gin.Context) {
file, _ := ctx.FormFile("uploadfile")
fmt.Println(file.Filename)
file_path := "upload/" + filepath.Base(file.Filename)
fmt.Println(file_path)
ctx.SaveUploadedFile(file, file_path)
ctx.String(http.StatusOK, "上传成功")
}
server.go
package main
import (
"main/controller"
"net/http""github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)/*
// 错误: server.go:4:2: package main/controller is not in GOROOT (/home/tiger/go/go/src/main/controller)
go mod init main//错误: server.go:7:2: no required module provides package github.com/gin-gonic/gin; to add it:
go get github.com/gin-gonic/gin//处理跨域框架
go get github.com/gin-contrib/cors
*//*
当客户端(尤其是基于 Web 的客户端)想要访问 API 时,服务器会决定允许哪些客户端发送请求。这是通过使用称为 CORS 来完成的,它代表跨源资源共享。
跨域资源共享 (CORS) 是一种机制,允许从提供第一个资源的域之外的另一个域请求网页上的受限资源。
*/func CrosHandler() gin.HandlerFunc {
return func(context *gin.Context) {
context.Writer.Header().Set("Access-Control-Allow-Origin", "*")
context.Header("Access-Control-Allow-Origin", "*") // 设置允许访问所有域
context.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE,UPDATE")
context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken")
context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
context.Header("Access-Control-Max-Age", "172800")
context.Header("Access-Control-Allow-Credentials", "true")
context.Set("content-type", "application/json") //设置返回格式是json
//处理请求
context.Next()
}
}// http://127.0.0.1:8181/ping
// http://127.0.0.1:8181/index
func main() {
r := gin.Default()// 设置全局跨域访问
//r.Use(CrosHandler())//cors处理跨域
r.Use(cors.Default())// 返回一个json数据
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
"num": 888,
})
})// 返回一个html页面
r.LoadHTMLGlob("templates/*")
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})r.POST("/login", controller.LoginPost)
r.POST("/formlogin", controller.FormLoginPost)
r.POST("/upload", controller.UploadFile)//r.Run() // <===> r.Run(":8080") 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8181")
}
templates/home.html
欢迎进入首页
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>欢迎进入首页</title>
</head>
<body>
<h3>登录测试</h3>
<hr/><form action="http://localhost:8282/formlogin" method="post">
<table border=0 title="测试">
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan=2>
<input type="reset" />
<input type="submit" value="登录" />
</td>
</tr>
</table>
</form>
<br>
<h3>文件上传测试</h3>
<hr/>
<form action="http://localhost:8282/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadfile"/>
<input type="submit" value="upload">
</form>
</body>
</html>
3.3单测效果
http://127.0.0.1:8181/ping
http://127.0.0.1:8181/index
上传文件:
8282端口nginx代理测试:
4.nginx
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
#location [ = | ~ | ~* | ^~ ] uri { }
# ~区分大小写的正则匹配;
# ~*不区分大小写的正则匹配;
# ^~常规字符串匹配;
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
fastcgi_intercept_errors on;
#gzip on;
limit_conn_zone $binary_remote_addr zone=addr:10m;
#代理静态页面
server {
listen 80;
server_name www.liudehua.com;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /home/tiger/nginx-1.22.1/nginx/html/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
#404配置
#error_page 404 /404.html;
#location /404.html {
# alias /home/tiger/nginx-1.22.1/nginx/html/dist;
# index index.html index.htm;
#}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
#代理真正的后台服务
server {
listen 8282;
location / {
proxy_pass http://127.0.0.1:8181/;
}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
部署前端程序:
启动nginx:
5.运行效果
http://www.liudehua.com/
可以看到nginx反向代理:
postman测试:
不代理测试:8181端口
代理测试:8282端口
6.问题总结
6.1 跨域访问错误:前后台一块解决
6.2 form表单请求,axios.post请求Go解析处理
6.2 vue中的正则表达式
vscode安装插件any-rule插件
ctrl + shift + p 输入密码,选择正则表达式