【Golang 1.18】GoGo垃圾信 - 求職技能三寶大集合

GoGo垃圾信,大家一起進入神乎其技境界吧。今天這篇呢,主是結合資料端 / 網頁後端 / 手機端,產生出求職三寶系統 - 簡訊 / 推播 / 垃圾信,雖然本篇是以Golang為主體,但在Vue CLI + Typescript的著墨上會比較多,也祝大家51勞動節快樂,也祝我下禮拜能買到快篩試劑,畢竟本土確診人數已經破萬人了啊,每週一戳的大時代來臨了。

做一個長得像這樣的東西

作業環境

項目 版本
CPU Apple M1
macOS Big Sur 12.3.1 arm64
Golang 1.18.1 arm64
Node.js 16.15.0 arm64
Visual Studio Code 1.66.2 arm64
Xcode 13.2 arm64
Android Studio 2021.1.1 arm64
Postman 9.0.9 arm64
Yarn 1.22.18
Vue CLI 5.0.4

Golang - 首先來看看後端API的部分

  • 因為Code的部分之前的幾篇都提到過了,這裡就不再細說,只會提比較重要的部分…
功能 API 方式
新增單一使用者 http://localhost:12345/user/ POST
搜尋使用者列表 http://localhost:12345/user/ GET
搜尋單一使用者 http://localhost:12345/user/<username> GET
更新單一使用者Token http://localhost:12345/user/<username> PATCH
給單一使用者發垃圾信 http://localhost:12345/mail/<username> POST
給單一使用者發垃圾推播 http://localhost:12345/push/<username> POST
給單一使用者發垃圾簡訊 http://localhost:12345/sms/<username> POST
package main

import (
	"net/http"

	"william/model"
	"william/utility"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

const DatabasePath string = "./file/push.sqlite3"

var User model.User

// [程式一開始要初始化的東西](https://blog.csdn.net/wide288/article/details/92808990)
func init() {
}

func main() {

	database, error := utility.Sqlite3.CreateDatabase(DatabasePath)

	if error != nil {
		utility.Println(error)
	}

	error = database.AutoMigrate(&model.User{})
	if error != nil {
		utility.Println(error)
	}

	routerSetting(database)
}

// [初始化Router相關設定](https://blog.csdn.net/fbbqt/article/details/114386445)
func routerSetting(database *gorm.DB) {

	corsConfig := cors.DefaultConfig() // 防止CORS的設定
	corsConfig.AllowAllOrigins = true  // 防止CORS的設定

	router := gin.Default()
	router.MaxMultipartMemory = 8 << 20

	router.Use(cors.New(corsConfig))
	registerWebAPI(router, database)
	router.Run(":12345")
}

// 註冊API
func registerWebAPI(router *gin.Engine, database *gorm.DB) {
	selectUser(router, database)
	selectAllUser(router, database)
	insertUser(router, database)
	updateUser(router, database)
	mailUser(router, database)
	pushNotificationUser(router, database)
}

// MARK: - WebAPI
// <GET>搜尋單一使用者 => http://localhost:12345/user/william
func selectUser(router *gin.Engine, database *gorm.DB) {

	router.GET("/user/:name", func(context *gin.Context) {

		name := context.Param("name")
		user, error := User.Select(database, name)

		if user.ID != 0 {
			utility.ContextJSON(context, http.StatusOK, user, nil)
			return
		}

		utility.ContextJSON(context, http.StatusOK, nil, error)
	})
}

// <GET>搜尋使用者列表 => http://localhost:12345/user
func selectAllUser(router *gin.Engine, database *gorm.DB) {

	router.GET("/user", func(context *gin.Context) {

		users := User.SelectAll(database)

		utility.ContextJSON(context, http.StatusOK, users, nil)
	})
}

// <POST>新增單一使用者 => http://localhost:12345/user + {"system":0,"name":"William","mail":"linuxice0609@gmail.com","phone":"0912345678"}
func insertUser(router *gin.Engine, database *gorm.DB) {

	router.POST("/user", func(context *gin.Context) {

		dictionary := utility.RequestBodyToMap(context)
		result, error := User.Insert(database, dictionary)

		utility.ContextJSON(context, http.StatusOK, result, error)
	})
}

// <PATCH>更新單一使用者Token => http://localhost:12345/user/william + {"system":0,"token":"72b3ee73e5314cc4c1991dc9377f1f2c1c123dbc492f5c4197ab11f330b7568f"}
func updateUser(router *gin.Engine, database *gorm.DB) {

	router.PATCH("/user/:name", func(context *gin.Context) {

		name := context.Param("name")
		dictionary := utility.RequestBodyToMap(context)

		system := model.MobileSystem(dictionary["system"].(float64))
		token := dictionary["token"].(string)

		info := map[string]interface{}{
			"system": system,
			"token":  token,
		}

		result, error := User.Update(database, name, info)
		utility.ContextJSON(context, http.StatusOK, result, error)
	})
}

// <POST>給單一使用者發垃圾信 => http://localhost:12345/mail/william + {"title":"垃圾信", "message":"什麼事也沒有"}
func mailUser(router *gin.Engine, database *gorm.DB) {

	router.POST("/mail/:name", func(context *gin.Context) {

		name := context.Param("name")
		dictionary := utility.RequestBodyToMap(context)

		title := dictionary["title"].(string)
		message := dictionary["message"].(string)

		result, error := User.EMail(database, name, title, message)
		utility.ContextJSON(context, http.StatusOK, result, error)
	})
}

// <POST>給單一使用者發垃圾推播 => http://localhost:12345/push/william + {"title":"垃圾信", "message":"什麼事也沒有"}
func pushNotificationUser(router *gin.Engine, database *gorm.DB) {

	router.POST("/push/:name", func(context *gin.Context) {

		name := context.Param("name")
		dictionary := utility.RequestBodyToMap(context)

		result, error := User.PushNotification(database, name, dictionary)
		utility.ContextJSON(context, http.StatusOK, result, error)
	})
}

// <POST>給單一使用者發垃圾簡訊 => http://localhost:12345/sms/william + {"message":"什麼事也沒有"}
func smsUser(router *gin.Engine, database *gorm.DB) {

	router.POST("/sms/:name", func(context *gin.Context) {

		name := context.Param("name")
		dictionary := utility.RequestBodyToMap(context)
		message := dictionary["message"].(string)

		result, error := User.SMS(database, name, message)

		utility.ContextJSON(context, http.StatusOK, result, error)
	})
}

➊ 新增單一使用者

{
  "system": 0,                        // 手機系統 (iOS: 0 / Android: 1)
  "name": "William",                  // 個人的姓名代號
  "mail": "linuxice0609@gmail.com",   // 電子信箱
  "phone": "0912345678"               // 手機號碼
}
{
  "error": null,
  "result": {
    "isSuccess": true                 // 新增結果 (成功:true / 失敗:false)
  }
}

➋ 搜尋使用者列表

  • 這裡就是後台要列出的列表,選擇要對誰發垃圾信?
  • HTTP Response
{
  "error": null,
  "result": [
    {
      "ID": 1,
      "CreatedAt": "2022-05-01T09:07:03.127434+08:00",
      "UpdatedAt": "2022-05-01T09:07:03.127434+08:00",
      "DeletedAt": null,
      "system": 0,
      "name": "William",
      "mail": "linuxice0609@gmail.com",
      "phone": "0912345678",
      "token": ""
    }
  ]
}

➌ 搜尋單一使用者

  • 取得單一使用者的相關資訊,手機號碼 / eMail / Token

➍ 更新單一使用者Token

  • 這個就是給手機端註冊、更新Token用的,有考慮到使用者有可能換手機系統,所以也是可以更新的…
  • HTTP Request
{
  "system": 0,                                                                  // 手機系統 (iOS: 0 / Android: 1)
  "token": "72b3ee73e5314cc4c1991dc9377f1f2c1c123dbc492f5c4197ab11f330b7568f"   // 手機Token
}
  • HTTP Response
{
  "error": null,
  "result": {
    "isSuccess": true                 // 新增結果 (成功:true / 失敗:false)
  }
}

➎ 給單一使用者發垃圾信

  • 這個就是計對單一使用者的eMail去發送訊息
  • HTTP Request
{
  "title": "垃圾信",           // 信件標題
  "message": "什麼事也沒有"     // 信件內容
}
  • HTTP Response
{
  "error": null,
  "result": {
    "isSuccess": true                 // 新增結果 (成功:true / 失敗:false)
  }
}

➏ 給單一使用者發垃圾推播

  • 這個就是計對單一使用者的手機Token去發送推播
  • HTTP Request
{
  "title": "垃圾推播",
  "message": "就是發垃圾~~~"
}
  • HTTP Response
{
  "error": null,
  "result": {
    "isSuccess": true                 // 新增結果 (成功:true / 失敗:false)
  }
}

➐ 給單一使用者發垃圾簡訊

{
  "message": "就是發垃圾~~~"
}
  • HTTP Response
{
  "error": null,
  "result": {
    "code": "00000",
    "msgid": 12345678,
    "text": "Success"
  }
}

iOS - 再來看看前端iOS的部分

import UIKit
import WWPrint
import WWNetworking

@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    var deviceToken: String?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        Utility.shared.userNotificationSetting(delegate: self) {
            wwPrint("Granted")
        } rejectedHandler: {
            wwPrint("Reject")
        } result: { (status) in
            wwPrint(status.rawValue)
        }
        
        return true
    }
}

// MARK: - 推播相關
extension AppDelegate {
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        self.deviceToken = deviceToken._hexString()
    }
}

// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.badge, .sound, .alert])
    }
}
import UIKit
import WWPrint
import WWNetworking
import WWHUD

final class ViewController: UIViewController {
    
    private let ApiUrl = "http://192.168.1.102:12345"
    
    override func viewDidLoad() { super.viewDidLoad() }

    @IBOutlet weak var usernameTextField: UITextField!
    @IBOutlet weak var eMailTextField: UITextField!
    @IBOutlet weak var phoneTextField: UITextField!

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        self.view.endEditing(true)
    }
    
    /// 使用者註冊
    @IBAction func registerUser(_ sender: UIBarButtonItem) {
        self.view.endEditing(true)
        self.register()
    }
    
    /// 使用者更新deviceToken
    @IBAction func upddateDeviceToken(_ sender: UIBarButtonItem) {
        
        guard let deviceToken = self.deviceToken() else { return }
        
        self.view.endEditing(true)
        self.update(deviceToken: deviceToken)
    }
}

// MARK: - 小工具
private extension ViewController {
    
    /// 取得推播的deviceToken
    func deviceToken() -> String? {
        
        guard let appDelegate = (UIApplication.shared.delegate) as? AppDelegate,
              let deviceToken = appDelegate.deviceToken
        else {
            return nil
        }

        return deviceToken
    }
    
    /// 使用者註冊
    func register() {
        
        let urlString = "\(ApiUrl)/user"
        
        let json: [String: Any?] = [
            "system": 0,
            "name": usernameTextField.text,
            "mail": eMailTextField.text,
            "phone": phoneTextField.text
        ]
        
        WWNetworking.shared.request(with: .POST, urlString: urlString, contentType: .json, paramaters: [:], headers: nil, httpBody: json._jsonSerialization()) { result in
            self.responseAction(result: result)
        }
    }
    
    /// 更新使用者deviceToken
    func update(deviceToken: String) {
        
        let urlString = "\(ApiUrl)/user/\(usernameTextField.text ?? "")"
        
        let json: [String: Any?] = [
            "system": 0,
            "token": deviceToken
        ]
        
        WWNetworking.shared.request(with: .PATCH, urlString: urlString, contentType: .json, paramaters: nil, headers: nil, httpBody: json._jsonSerialization()) { result in
            self.responseAction(result: result)
        }
    }
    
    /// 顯示提示的HUD
    func flashHud(isSuccess: Bool) {
        
        var effect = WWHUD.AnimationEffect.shake(image: #imageLiteral(resourceName: "success"), angle: 10.0, duration: 0.25)
        var backgroundColor = UIColor.yellow.withAlphaComponent(0.3)
        
        defer {
            DispatchQueue.main.async {
                WWHUD.shared.flash(effect: effect, height: 250, backgroundColor: backgroundColor, animation: 0.5, options: .curveEaseIn, completion: nil)
            }
        }
        
        if (!isSuccess) {
            effect = .shake(image: #imageLiteral(resourceName: "fail"), angle: 10.0, duration: 0.25)
            backgroundColor = UIColor.gray.withAlphaComponent(0.3)
        }
    }
    
    /// 取得Response的處理
    func responseAction(result: Result<WWNetworking.ResponseInformation, Error>) {
        
        var isSuccess = false
        
        defer { self.flashHud(isSuccess: isSuccess) }
        
        switch result {
        case .failure(let error): wwPrint(error)
        case .success(let info):
            
            guard let json = info.data?._jsonObject(),
                  let dictionary = json as? [String: Any],
                  let result = dictionary["result"] as? [String: Any],
                  let _isSuccess = result["isSuccess"] as? Bool
            else {
                return
            }
            
            isSuccess = _isSuccess
        }
    }
}

Android - 再來看看前端Android的部分

package idv.william.example

import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging
import okhttp3.*
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.IOException

class MainActivity : AppCompatActivity() {

    private lateinit var toolbar: Toolbar
    private lateinit var usernameEditText: EditText
    private lateinit var eMailEditText: EditText
    private lateinit var phoneEditText: EditText
    private lateinit var token: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initSetting()

        FirebaseMessaging.getInstance().subscribeToTopic("news")
        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
            if (!task.isSuccessful) return@OnCompleteListener
            token = task.result
        })
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.option_menu, menu)
        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {

        return when (item.itemId) {
            R.id.action_item_register -> { registerUser(); true }
            R.id.action_item_setting ->{ settingUser(); true }
            else -> { super.onOptionsItemSelected(item) }
        }
    }

    // 初始化設定
    private fun initSetting() {

        toolbar = this.findViewById(R.id.toolbar)
        toolbar.title = "GoGo垃圾信"

        usernameEditText = this.findViewById(R.id.username)
        eMailEditText = this.findViewById(R.id.eMail)
        phoneEditText = this.findViewById(R.id.phone)

        setSupportActionBar(toolbar)
    }

    // 顯示提示Toast
    private fun showToast(text: String) {
        Log.d("[TOAST]", text)
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
    }

    // 產生Request (POST)
    private fun requestPostMaker(urlString: String, json: String): Request {
        val body = json.toRequestBody()
        this.showToast(urlString)
        return Request.Builder().url(urlString).post(body).build()
    }

    // 產生Request (PATCH)
    private fun requestPatchMaker(urlString: String, json: String): Request {
        val body = json.toRequestBody()
        return Request.Builder().url(urlString).patch(body).build()
    }

    // 註冊User
    private fun registerUser() {

        val map = mapOf(
            "system" to 1,
            "name" to usernameEditText.text.toString(),
            "mail" to eMailEditText.text.toString(),
            "phone" to phoneEditText.text.toString()
        )

        val urlString = "http://192.168.1.103:12345/user/"
        val json = JSONObject(map).toString()
        val request = this.requestPostMaker(urlString = urlString, json = json)

        OkHttpClient().newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, error: IOException) { runOnUiThread { showToast(text = error.toString()) } }
            override fun onResponse(call: Call, response: Response) { runOnUiThread { showToast(text = "成功") } }
        })
    }

    // 更新Token
    private fun settingUser() {

        val map = mapOf(
            "system" to 1,
            "token" to token,
        )

        val urlString = "http://192.168.1.103:12345/user/" + usernameEditText.text.toString()
        val json = JSONObject(map).toString()
        val request = this.requestPatchMaker(urlString = urlString, json = json)

        OkHttpClient().newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, error: IOException) { runOnUiThread { showToast(text = error.toString()) } }
            override fun onResponse(call: Call, response: Response) { runOnUiThread { showToast(text = "成功") } }
        })
    }
}
package idv.william.example

import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class FCMMessageReceiverService : FirebaseMessagingService() {

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        Log.d("[TOKEN]", token)
    }

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        message.notification?.body?.let { Log.d("[TOKEN]", it) }
    }
}

Vue - 再來看看後端Vue的部分

➊ Node.js + Yarn

  • 安裝Node.js細節就不多做說明,可以參考之前的相關文章
  • 使用NPM加強版的Yarn來管理相關的套件
nvm install 16.15
npm install yarn --g
node -p process.arch
yarn global add @vue/cli

➋ Vue Command-Line Interface

npm install @vue/cli -g

➌ 建立Vue專案

vue create go-mail-and-push
cd go-mail-and-push
yarn serve

➍ 安裝網路套件 - axios

yarn add axios          // axios套件
yarn add @types/axios   // axios套件ts翻譯檔

➎ 安裝元件套件 - element-plus

yarn add element-plus   // 基於 Vue 3,設計師和開發者的套件庫

➏ 簡單來測試一下吧

// main.ts
import { createApp } from 'vue'
import App from './App.vue'

import ElementPlus from 'element-plus';
import locale from 'element-plus/lib/locale/lang/zh-tw'
import 'element-plus/theme-chalk/index.css'

createApp(App).use(ElementPlus, { locale }).mount('#app')
<!-- App.vue -->
<template>
  <div class="app">
    <el-header>GoGo垃圾信</el-header>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'App',
  components: {
  },
  setup() {
    return {}
  }
});
</script>

<style>
#app {
  text-align: center;
  color: #c3114c;
  margin-top: 60px;
}
</style>

➐ 連接Golang API

  • 這裡我們使用axios這個套件來打API,過程中筆者的碰到CORS的問題,簡單來說就是不能跨網域去打API,所以要在golang的部分加上設定…
func routerSetting(database *gorm.DB) {
	corsConfig := cors.DefaultConfig() // 防止CORS的設定
	corsConfig.AllowAllOrigins = true  // 防止CORS的設定
}
<template>
  <div class="app">
    <el-header>GoGo垃圾信</el-header>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'

export default defineComponent({
  name: 'App',
  components: {
  },
  setup() {
      GolangAPI: {
        userList: "http://localhost:12345/user",
      }
    }

    const API = {

      /// 取得使用者List
      userList: async (url: string) => {
        
        const response = await axios.get(url)
        const { result, _ } = response.data
        const users = result as any[]

        return users
      }
    }

    API.userList(Constant.GolangAPI.userList).then((_users) => {
      console.table(_users)
    })

    return {}
  }
});
</script>

<style>
#app {
  text-align: center;
  color: #c3114c;
  margin-top: 60px;
}
</style>

➑ 有類型的JavaScript

  • TypeScript的橫空出世,就是為了解決JavaScript寫法太過自由的問題,加上是弱類型的語言,有錯誤都會在執行後才會被發現…
  • 另外就是IDE的自動提示的問題,因為TypeScript是有類型的,所以在提示function上就會抓得很精準,不能用的就不會出現…
  • 產生有類型的ClassEnum,這也是js的一個痛點…
// SendTypeEnum.ts
type SendTypeEnum = 'eMail' | 'Push' | 'SMS'

export default SendTypeEnum
// User.ts
interface User {
    name: string,
    mail: string,
    system: number,
    token: string,
}

export default User

➒ Vue2 Option API => Vue3 Composition API

<template>
  <div class="app">
    <el-header>GoGo垃圾信</el-header>

    <!-- 資料 -->
    <el-table :data="users">
      <el-table-column prop="name" :label="userLabel.name" width="100"/>
      <el-table-column prop="mail" :label="userLabel.mail" width="256"/>
      <el-table-column prop="system" :label="userLabel.system" width="100"/>
      <el-table-column prop="token" :label="userLabel.token"/>
      <el-table-column fixed="right" :label="userLabel.operations" width="200">
        <template #default>
          <el-button color="#626aef" round @click="">垃圾信</el-button>
          <el-button color="#F1F30A" round @click="">垃圾推播</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import axios from 'axios'
import User from "./models/User";

export default defineComponent({
  name: 'App',
  components: {
  },
  setup() {

    const Constant = {

      UserLabel: ref({
        name: "姓名",
        mail: "電子信箱",
        system: "系統",
        token: "推播",
        operations: "功能",
        title: "標題",
        message: "內容"
      }),

      GolangAPI: {
        userList: "http://localhost:12345/user",
      }
    }
    
    const API = {

      /// 取得使用者List
      userList: async (url: string) => {
        
        const response = await axios.get(url)
        const { result, _ } = response.data
        const users = result as any[]

        return users
      }
    }

    let users = ref<User[]>([])

    API.userList(Constant.GolangAPI.userList).then((_users) => {
        _users.forEach((user: any) => {
          const _user = { name: user.name, mail: user.mail, system: user.system, token: user.token }
          users.value.push(_user)
        })
    })

    return { users, userLabel: Constant.UserLabel }
  }
});
</script>

<style>
#app {
  text-align: center;
  color: #c3114c;
  margin-top: 60px;
}
</style>

➓ GoGo垃圾信

  • 利用@click的功能來發送垃圾信,同時也利用el-dialog來產生一個編譯框
<template>
  <div class="app">
    <el-header>GoGo垃圾信</el-header>

    <!-- 資料 -->
    <el-table :data="users">
      <el-table-column prop="name" :label="userLabel.name" width="80"/>
      <el-table-column prop="mail" :label="userLabel.mail" width="200"/>
      <el-table-column prop="phone" :label="userLabel.phone" width="120"/>
      <el-table-column prop="system" :label="userLabel.system" width="60"/>
      <el-table-column prop="token" :label="userLabel.token"/>
      <el-table-column fixed="right" :label="userLabel.operations" width="300">
        <template #default="scope">
          <el-button color="#626aef" round @click="handleVisibleDialog(scope.row, true, 'eMail')">垃圾信</el-button>
          <el-button color="#F1F30A" round @click="handleVisibleDialog(scope.row, true, 'Push')">垃圾推播</el-button>
          <el-button color="#77B068" round @click="handleVisibleDialog(scope.row, true, 'SMS')">垃圾簡訊</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 編譯框 -->
    <el-dialog v-model="isVisibleDialog" :title="userLabel.dialogTitle" background="red">
      <el-form-item :label="userLabel.title" label-width="320"><el-input v-model="temp.title" autocomplete="off" /></el-form-item>  
      <el-form-item :label="userLabel.message" label-width="320"><el-input v-model="temp.message" autocomplete="off" /></el-form-item>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="handleVisibleDialog(null, false, '')">Cancel</el-button>
          <el-button type="primary" @click="handleAction(username)">Confirm</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import axios from 'axios'
import User from "./models/User";
import SendTypeEnum from "./models/SendTypeEnum";

export default defineComponent({
  name: 'App',
  components: {
  },
  setup() {

    const Constant = {

      UserLabel: ref({
        name: "姓名",
        mail: "電子信箱",
        system: "系統",
        phone: "手機號碼",
        token: "推播",
        operations: "功能",
        title: "標題",
        message: "內容",
        dialogTitle: "發送垃圾信"
      }),

      GolangAPI: {
        userList: "http://localhost:12345/user",
        sendMail: "http://localhost:12345/mail",
        pushNotification: "http://localhost:12345/push",
        sendMessage: "http://localhost:12345/sms",
      }
    }

    const API = {

      /// 取得使用者List
      userList: async (url: string) => {
        
        const response = await axios.get(url)
        const { result, _ } = response.data
        const users = result as any[]

        return users
      },

      /// 發發垃圾信
      sendMail: async (name: string) => {

        const url = `${Constant.GolangAPI.sendMail}/${name}`
        const response = await axios.post(url, {
          "title": tempFormInformation.value.title, 
          "message": tempFormInformation.value.message
        })

        const { result, _ } = response.data
        return result
      },

      /// 發發垃圾推播
      pushNotification: async (name: string) => {

        const url = `${Constant.GolangAPI.pushNotification}/${name}`
        const response = await axios.post(url, {
          "title": tempFormInformation.value.title, 
          "message": tempFormInformation.value.message
        })

        const { result, _ } = response.data
        return result
      },

      /// 發發垃圾簡訊
      sendMessage: async (name: string) => {

        const url = `${Constant.GolangAPI.sendMessage}/${name}`
        const response = await axios.post(url, {
          "title": tempFormInformation.value.title, 
          "message": tempFormInformation.value.message
        })

        const { result, _ } = response.data
        return result
      },
    }

    let users = ref<User[]>([])
    let username = ref("")
    let isVisibleDialog = ref(false)
    let sendType = ref<SendTypeEnum>("eMail")
    let tempFormInformation = ref({ title: "", message: "" })

    /// 取得User列表
    API.userList(Constant.GolangAPI.userList).then((_users) => {
        _users.forEach((user: any) => {
          const system = (user.system !== 0) ? 'Android' : 'iOS'
          const _user = { name: user.name, mail: user.mail, phone: user.phone, system: system, token: user.token }
          users.value.push(_user)
        })
    })

    /// Dialog開關
    const handleVisibleDialog = (user: User, isVisible: boolean, type: SendTypeEnum) => {

      isVisibleDialog.value = isVisible
      sendType.value = type
      username.value = user.name

      switch (type) {
        case "eMail": Constant.UserLabel.value.dialogTitle = `🥳 發送垃圾信`; break
        case "Push": Constant.UserLabel.value.dialogTitle = `😍 發送垃圾推播`; break
        case "SMS": Constant.UserLabel.value.dialogTitle = `😀 發送垃圾簡訊`; break
        default: break
      }

      tempFormInformation.value = { title: "", message: "" }
    }

    /// 發送垃圾信 / 發送垃圾推播
    const handleAction = (name: string) => {
      
      switch (sendType.value) {
        case "eMail": API.sendMail(name); break
        case "Push": API.pushNotification(name); break
        case "SMS": API.sendMessage(name); break
        default: break
      }

      isVisibleDialog.value = false
      tempFormInformation.value = { title: "", message: "" }
    }

    return { users, username, userLabel: Constant.UserLabel, handleVisibleDialog, handleAction, temp: tempFormInformation, isVisibleDialog }
  }
});
</script>

<style>
#app {
  text-align: center;
  color: #c3114c;
  margin-top: 60px;
}
</style>

產生美美的API文件UI - Swagger

編譯SourceCode

make build

放置編譯好的執行檔

go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files
./swag

產生文件的JSON檔

  • 這裡就是由swagger產生文件的動作,相關的格式請參考官方的文件
./swag init
go run .

SwaggerUI

  • 最後就是重複的產生文件,然後執行…
  • http://localhost:12345/swagger/index.html
package main

import (

	"william/docs"

	swaggerfiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
)

// 註冊API
func registerWebAPI(router *gin.Engine, database *gorm.DB) {
	swaggerApiDoc(router)
}

/// MARK: - WebAPI
// 產生DOC文件UI介面
func swaggerApiDoc(router *gin.Engine) {
	docs.SwaggerInfo.BasePath = "/"
	router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
}

// @BasePath /
// @schemes http https
// @Router /user/{name} [get]
// @param name path string true "使用者名稱"
// @Tags 搜尋單一使用者
// @summary <GET>搜尋單一使用者 => http://localhost:12345/user/william
func selectUser(router *gin.Engine, database *gorm.DB) {

	router.GET("/user/:name", func(context *gin.Context) {

		name := context.Param("name")
		user, error := User.Select(database, name)

		if user.ID != 0 {
			utility.ContextJSON(context, http.StatusOK, user, nil)
			return
		}

		utility.ContextJSON(context, http.StatusOK, nil, error)
	})
}

範例程式碼下載 - Golang / Vue / iOS / Android

後記

其實世界上真的沒有什麼東西是可以一步登天的吧?常常有人說我寫的東西都太簡單了,是我用詞太生活化了嗎?不夠專業?難怪我都交不到女朋友;也有人說我比較像PM學得廣但不深,但容易跨部門溝通;我也相信人外有人,天外有天,每個人都有優於常人之處;在工作場合也是如此,每次大家說的都是同一件事,但因為學習的東西不同,造成『一個事情,各自表述』;不過我真的是很弱,要花雙倍的時間學東西,但至少還是有在進步的;我總是覺得:『唉呀,這Code沒什麼嘛,就這樣…那樣而已』,這種Code才是好的Code,您說呢?