【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)
	})
}
➊ 新增單一使用者
- 也就是註冊個人資訊,騙你的個資?
     - HTTP Request
 
{
  "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)
  }
}
➏ 給單一使用者發垃圾推播
{
  "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
nvm install 16.15
npm install yarn --g
node -p process.arch
yarn global add @vue/cli
➋ Vue Command-Line Interface
- 安裝Vue CLI
     
npm install @vue/cli -g
➌ 建立Vue專案
- 這裡我們使用VueCLI來建立一個叫go-mail-and-push的專案
 - 按圖施工,保證成功
 
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
- 一直為UI長相苦手的我,還是乖乖裝裝element-plus這個套件吧,安裝過的套件,都會記錄在package.json這個檔上面…
 
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
func routerSetting(database *gorm.DB) {
	corsConfig := cors.DefaultConfig() // 防止CORS的設定
	corsConfig.AllowAllOrigins = true  // 防止CORS的設定
}
- 取得User列表 => http://localhost:12345/user
     
<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上就會抓得很精準,不能用的就不會出現…
     - 產生有類型的Class跟Enum,這也是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
- Vue 3的Composition API寫起來其實滿直覺的,最主要把一些設定通通放在setup()之內…
 - 最主要的,只要是需要綁定的變數,通通要加上一層ref包裝一下…
 - 這裡就借助el-table來幫我們跑迴圈…
    
     
<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垃圾信
<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
- 首先下載swag的SourceCode,因為呢官方編的呢沒有M1的版本,所以…就自己編…
     
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,您說呢?