前言
为什么选择go+vue,博主一直没有接触过java开发。上学时学的东西忘的差不多了。系统功能很简单,但是要部署在客户的服务器上。go的一功能是将代码和静态资源打包成一个二进制执行文件,比较符合我的胃口。心心念一直想入坑go开发。
总体设计
系统很简单,只有两个页面。一个登录页面,登录后到达主页面。主页面包括一个文件上传组件,一个table组件。table组件,记录上传处理记录,下载处理结果。采用vue开发。后端接口采用go语言开发。
前端
后端
采用go语言开发后端接口,接口包含如下四个。同时还有其他一些细节,如将数据库连接,用户密码,程序监听端口等设置成可配置,配置在运行文件同目录的.env文件内
开发过程
大部分是chatgpt的功劳,在chatgpt回复的基础上修改。一开始一个main.go到底。后续将不同功能拆成多个文件。
功能代码
main函数
实现所有路由,加载静态资源,监听端口可配置,记录日志,中断程序保存日志
func main() {
// 日志记录
initLogger()
// 捕获 SIGINT 信号(Ctrl+C)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGINT)
go func() {
<-c
logger.Println("Received SIGINT. Flushing logs and exiting.")
closeLogger()
os.Exit(0)
}()
// 路由
r := mux.NewRouter()
// 处理文件下载路由
r.HandleFunc("/api/download", downloadHandler)
// 处理文件上传路由
r.HandleFunc("/api/upload", uploadHandler)
// 新增处理读取任务信息的路由
r.HandleFunc("/api/readtasks", readTasksHandler)
// 添加处理登录请求的路由
r.HandleFunc("/api/login", loginHandler).Methods("POST")
// 设置静态文件服务
statikFS, err := fs.New()
if err != nil {
log.Fatal(err)
}
r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(statikFS)))
// 加载 .env 文件中的环境变量
if err := godotenv.Load(); err != nil {
logger.Fatal("加载.env失败")
return
}
lport := os.Getenv("LISTEN_PORT")
logger.Printf("Server is listening on port %s...", lport)
log.Fatal(http.ListenAndServe(":"+lport, r))
}
日志函数
日志同时记录到文件
func initLogger() {
var err error
logFile, err = os.OpenFile("smsman.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
logger = log.New(io.MultiWriter(os.Stdout, logFile), "LOG: ", log.Ldate|log.Ltime|log.Lshortfile)
}
func closeLogger() {
if logFile != nil {
logFile.Close()
}
}
下载函数
实现处理结果下载功能
func downloadHandler(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("filename") // 获取请求中的文件名参数
filePath := "./result/" + filename // 构建文件路径
// 打开文件
file, err := os.Open(filePath)
if err != nil {
http.Error(w, "文件未找到", http.StatusNotFound)
return
}
defer file.Close()
// 设置响应头
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(filePath))
w.Header().Set("Content-Type", "application/octet-stream")
fileInfo, _ := file.Stat()
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
// 传输文件内容到 ResponseWriter
_, err = io.Copy(w, file)
if err != nil {
http.Error(w, "文件传输失败", http.StatusInternalServerError)
return
}
}
登录认证函数
实现简单的登录认证
func loginHandler(w http.ResponseWriter, r *http.Request) {
// 解析请求中的用户名和密码
r.ParseForm()
username := r.Form.Get("username")
passwd := r.Form.Get("password")
// 加载 .env 文件中的环境变量
if err := godotenv.Load(); err != nil {
logger.Fatal("加载.env失败")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user := os.Getenv("SMSUSER")
password := os.Getenv("SMSPWD")
// 验证用户名和密码
if username == user && passwd == password {
response := LoginResponse{
Success: true,
Username: username,
}
jsonResponse, err := json.Marshal(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(jsonResponse)
} else {
response := LoginResponse{
Success: false,
Username: username,
}
jsonResponse, err := json.Marshal(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(jsonResponse)
}
}
前端页面开发
前端页面开发采用vue,这里用到了 viewui, 下载他们的demo,然后改造。
Home.vue
Home.vue中加载了两个组件,一个是文件上传组件Fupload.vue,另一个是Mtable.vue。记录一下加载方法。
<template>
<div class="layout-base">
<Layout>
<Header class="layout-header">短信签名处理后台</Header>
<Content class="layout-content">
<Card style="width:100%">
<template #title>
<p style="text-align: left; margin: 0;">文件上传</p>
</template>
<Fupload/>
</Card>
<Divider>---</Divider>
<Card style="width:100%;">
<template #title>
<p style="text-align: left; margin: 0;">上传记录</p>
</template>
<Mtable/>
</Card>
</Content>
</Layout>
</div>
</template>
<script>
import Fupload from '@/components/Fupload.vue'
import Mtable from '@/components/Mtable.vue'
export default {
name: 'HomeView',
components: {
Fupload,
Mtable
},
data () {
return {
split: 0.4
}
},
}
</script>
Mtable.vue
Mtable.vue 主要注意每个2s刷新数据操作
created() {
this.fetchData();
// 每2秒刷新数据
setInterval(() => {
this.fetchData();
}, 2000);
},
methods: {
async fetchData() {
try {
const response = await axios.get('/api/readtasks');
this.data = response
.data; // Assuming the response directly contains the array of task objects
} catch (error) {
console.error('Error fetching data:', error);
}
},
getStatusColor(status) {
return {
'color-green': status === '进行中',
'color-red': status === '已完成'
};
}
}
登录检测
默认访问Home.vue,同时检测是否登录。如果没有登录的话,跳转到Login.vue
// router/index.js
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// 检查用户是否已登录,这里假设 isAuthenticated 是存储登录状态的变量
const isAuthenticated = store.state.isAuthenticated;
if (!isAuthenticated) {
next('/login'); // 如果未登录,则重定向到登录页面
} else {
next();
}
} else {
next(); // 不需要登录认证的页面直接放行
}
})
// store/index.js
// 存储登录状态
export default createStore({
state: {
isAuthenticated: false // 初始登录状态为未认证
},
getters: {
},
mutations: {
setAuthentication(state, isAuthenticated) {
state.isAuthenticated = isAuthenticated;
}
},
actions: {
login({ commit }) {
// 模拟登录成功,设置登录状态为 true
commit('setAuthentication', true);
localStorage.setItem('isAuthenticated', true);
},
logout({ commit }) {
// 登出操作,设置登录状态为 false
commit('setAuthentication', false);
}
},
modules: {
}
})
import axios from 'axios';
import { mapActions } from 'vuex';
import router from '../router'; // 引入 Vue Router
export default {
data() {
return {
username:'',
password:''
}
},
methods: {
...mapActions(['login']),
async handleSubmit(valid, {
username,
password
}) {
if (valid) {
try {
const params = new URLSearchParams();
params.append('username', username);
params.append('password', password);
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
};
const response = await axios.post('/api/login', params, config);
//console.log(response)
if (response.data.success) {
this.$Modal.success({
title: '登录成功',
content: '欢迎回来,' + username
});
this.login();
router.push('/'); // 登录成功后跳转到 /home 页面
} else {
this.$Modal.error({
title: '登录失败',
content: '用户名或密码错误,请重试'
});
}
} catch (error) {
console.error(error);
this.$Modal.error({
title: '登录失败',
content: '出现意外错误,请稍后再试'
});
}
}
}
}
}
程序打包
mac安装go-statik, 然后打包
brew install go-statik
statik -src=./dist/
#man - centos
GOOS=linux GOARCH=amd64 go build -o smsman_linux_amd64 main.go
评论 (0)