異動先の部署でのキックオフの事務局長になったのは良いのですが、異動先の部署では、Teamsを使い倒しているので、四苦八苦しています。
何とか前任者と同じ「かっこいい」フォームを使って、チームへの投稿をしたいのですが、やりかたが分からくて、同僚に泣きついています。
で、先程教えて貰ったページを忘れない内に記載しておきます。
Teamsでアナウンスを投稿する方法
江端智一のホームページ
異動先の部署でのキックオフの事務局長になったのは良いのですが、異動先の部署では、Teamsを使い倒しているので、四苦八苦しています。
何とか前任者と同じ「かっこいい」フォームを使って、チームへの投稿をしたいのですが、やりかたが分からくて、同僚に泣きついています。
で、先程教えて貰ったページを忘れない内に記載しておきます。
https://teratail.com/questions/2m8dsx1urnslib
一応動いているソースコード
// main.go
package main
import (
"flag"
"fmt"
"log"
"net/http"
"net/url"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{}
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
TYPE string `json:"type"` // "PERSON","BUS","CONTROL
POPUP int `json:"popup"`
//Address string `json:"address"`
}
func echo2(w http.ResponseWriter, r *http.Request) {
conn2, err := upgrader.Upgrade(w, r, nil) //conn2でwebsocketを作成
if err != nil {
log.Println("websocket connection err:", err)
return
}
defer conn2.Close()
for {
gl2 := new(GetLoc)
err := conn2.ReadJSON(&gl2)
if err != nil {
log.Println("ReadJSON:", err)
break
}
fmt.Println("echo2:", gl2)
//time.Sleep(time.Second * 10)
}
}
var addr = flag.String("addr", "0.0.0.0:5000", "http service address") // テスト
func dummyClient() {
_ = websocket.Upgrader{} // use default options
flag.Parse()
log.SetFlags(0)
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
for {
gl := new(GetLoc)
gl.ID = 101
gl.Lat = 181.0
gl.Lng = 181.0
gl.TYPE = "BUS"
gl.POPUP = 101
err := c.WriteJSON(gl)
if err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/echo2", echo2)
//go dummyClient()
log.Println("server starting...", "http://localhost:5000")
log.Fatal(http.ListenAndServe("localhost:5000", nil))
}
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test</title>
</head>
<body>
<form>
<button id="open">open</button>
<button id="send">send</button>
<button id="close">close</button>
</form>
<script>
var ws;
// websocketのオープン(この段階で接続完了)
ws = new WebSocket('ws://localhost:5000/echo2') // ユーザ登録画面
// websocketのオープンイベントが発生したら
ws.onopen = function(evt) {
// 何もしない
print("OPEN");
console.log("OPEN");
return false;
}
// "send"ボタンをクリックしたら
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
var obj = {id:101,lat:181.0, lng:181.0,type:"BUS", popup:101}
var json_obj = JSON.stringify(obj);
ws.send(json_obj);
return false;
};
// "close"ボタンをクリックしたら
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
// websocketを閉じる
ws.close();
console.log("CLOSE")
return false;
};
</script>
</body>
</html>
https://github.com/gorilla/websocket/tree/master/examples/echo
この"server.go"は、client.goからの通信だけではなく、ブラウザの画面も提供します(スゴい!)。
というか、Goプログラムの中に、html書けるなんて知らんかった。
// server.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// +build ignore
package main
import (
"flag"
"html/template"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = c.WriteMessage(mt, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
output.scroll(0, output.scrollHeight);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="Hello world!">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output" style="max-height: 70vh;overflow-y: scroll;"></div>
</td></tr></table>
</body>
</html>
`))
// client.go
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// +build ignore
package main
import (
"flag"
"log"
"net/url"
"os"
"os/signal"
"time"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "localhost:8080", "http service address")
func main() {
flag.Parse()
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-done:
return
case t := <-ticker.C:
err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
if err != nil {
log.Println("write:", err)
return
}
case <-interrupt:
log.Println("interrupt")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
leafletの地図上に、マウスでクリックするとマーカーが出てきて、マーカーをクリックするとマーカーが消える、というJavaScript
var map = L.map("map", { attributionControl: false, zoomControl: false }).setView(new L.LatLng(35.654543, 139.795534), 18); // }).setView(new L.LatLng(35.598563, 139.475528), 18); 広袴 L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', { detectRetina: true, maxNativeZoom: 18 }).addTo(map); var leafletView = new PruneClusterForLeaflet(1,1); // (120,20)がデフォルト //地図のclickイベントでonMapClick関数を呼び出し map.on('click', onMapClick) function onMapClick(e) { //地図のclickイベント呼び出される //クリック地点の座標にマーカーを追加、マーカーのclickイベントでonMarkerClick関数を呼び出し var mk = L.marker(e.latlng).on('click', onMarkerClick).addTo(map); console.log("click:",e.latlng) } function onMarkerClick(e) { //マーカーのclickイベント呼び出される //クリックされたマーカーを地図のレイヤから削除する map.removeLayer(e.target); console.log("remove:",e.target) }
から、1日もあれば、動かせるだろうと思ったのですが、そこから、redisのパブサブ、Golangのmap、JavaScriptの改造、えらい目に会いましたが、ようやく目処が立ちました。
PrumeMobileは、Webのメモリを使うので、単なるWebサーバのようには取り扱えないので、色々工夫をしました。デバッグで苦労しました。
嬉しい気持ちは、直ぐに消えてしまうので、消える前に記録しておきました。
// パブリッシュ側
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
TYPE string `json:"type"` // "PERSON","BUS","CONTROL
POPUP int `json:"popup"`
//Address string `json:"address"`
}
gl.TYPE = "BUS"
gl.Lat = 35.654543 + (rand.Float64()-0.5)*0.00001*20
gl.Lng = 139.795534 + (rand.Float64()-0.5)*0.00002*20
gl.ID = rand.Int() % 5
json_gl, _ := json.Marshal(gl)
r, err := redis.Int(conn.Do("PUBLISH", "channel_1", json_gl))
// サブスクライブ側
for {
switch v := psc.Receive().(type) {
case redis.Message:
fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
var gl GetLoc
_ = json.Unmarshal(v.Data, &gl)
fmt.Println(gl.ID)
var gl GetLoc
_ = json.Unmarshal(v.Data, &gl)
fmt.Println(gl.ID)
//conn.WriteJSON(v.Data)
conn.WriteJSON(gl)
JavaScriptで受けとる
<script>
function obj(id, lat, lng, type, popup){
this.id = id;
this.lat = lat;
this.lng = lng;
this.type = type;
this.popup = popup;
}
//ws.onmessage = e => console.log(e.data)
ws.onmessage = function(event) { // 受信したメッセージはここに飛んでくる
console.log("RESPONSE",event.data)
var obj = JSON.parse(event.data);
console.log("after parse:",obj.id)
}
ポイントは、Marshal、Unmarshal、
parseの使い方
入力2、出力1のマップを取扱いたい場合のケースを調べてみました。
Golangは、2次元マップの取扱いは簡単なのですが、N次元は、かなり複雑になるようです。調べたところ、以下のような取扱いができることが分かりました。
package main
import "fmt"
func main() {
// 2次元マップ
m := map[string]int{}
//m := make(map[string]int)
m["apple"] = 150
m["banana"] = 200
m["lemon"] = 300
fmt.Println("0:", m["apple"])
// 多次元mapは、2次元マップのように簡単には使えない
// 比較的面倒のないやり方は、以下のように構造体を使う方法(らしい)
// https://qiita.com/ruiu/items/476f65e7cec07fd3d4d7
type key struct {
id int
att string
}
// 配列宣言
m1 := make(map[key]int)
// レコード追加
m1[key{0, "BUS"}] = 1
m1[key{0, "PERSON"}] = 1
m1[key{1, "BUS"}] = 2
m1[key{1, "PERSON"}] = 11
// レコードの一つを表示
fmt.Println("1:", m1[key{1, "BUS"}])
// レコードの全部を表示
fmt.Println("2:", m1)
// 変数を使う方法
id0 := 1
att0 := "PERSON"
fmt.Println("3:", m1[key{id0, att0}])
// レコードの削除
delete(m1, key{1, "BUS"})
fmt.Println("4:", m1)
for i := range m1 {
fmt.Println("5:", i)
}
// 変数を使って、キーの存在を確認する
id0 = 1
att0 = "PERSON"
value, isThere := m1[key{id0, att0}]
fmt.Println("6:", value, isThere)
id0 = 2
att0 = "PERSON"
value, isThere = m1[key{id0, att0}]
fmt.Println("7:", value, isThere)
// レコード追加の例
id0 = 3
att0 = "BUS"
new_id0 := 20
fmt.Println("8:", m1)
_, isThere = m1[key{id0, att0}]
if !isThere {
m1[key{id0, att0}] = new_id0
}
fmt.Println("9:", m1)
}
出力結果は以下の通りです。
C:\Users\ebata\goga\1-11\test>go run main.go
0: 150
1: 2
2: map[{0 BUS}:1 {0 PERSON}:1 {1 BUS}:2 {1 PERSON}:11]
3: 11
4: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11]
5: {1 PERSON}
5: {0 BUS}
5: {0 PERSON}
6: 11 true
7: 0 false
8: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11]
9: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11 {3 BUS}:20]
以上
で、型番を調べたら、
(1)redisサーバを援用して、(2)golangをサーバにして、(3)websocketでブロードキャストが実現できて、(4)JavaScriptをクライアントとして使える、(5)OSSを捜し出しました。
理由は、複数のWebに同じ地図と車両の移動をリアルタイムを表示しなればならないのですが、Websocketはユニキャスト通信しかできないので、困っていました(UDPとか使えればいいんですが、スマホでUDPが通るとは思えないので)。
WebSocketでブロードキャストを作るのが辛いので、これを援用したいと思います。
これは、http://localhost:5000 でブラウザを立ち上げると、サーバを介してブラウザ→ブラウザにメッセージがechoされます。
redisサーバを立ち上げておいて、main.goと、js/index.htmlだけで動きます。
上記を改造すれば、サーバからパブ(pub)、webブラウザでサブ(sub)が可能になるはずです ―― 多分
実験した結果、subscribeしたオブジェクトに、過去のデータもpublishされることが分かりました。
subscribe以前のデータを取込むと逆に困る(過去のデータは不要な上に、データの表示もバラバラになる)ので、pubsub-goの採用は見送り、redigoの方を使うことにしました。
JavaScriptを直接のクライアントとして使いたかったのですが、redigoと連携するJavaScriptが見つけられなかった(かなり捜したつもり)ので、Golangで立ち上げるハンドルの中で、個別に対処することにしました。
golangのプログラムの中でブロードキャストするのであれば、
https://github.com/MicrosoftArchive/redis/releases/tag/win-3.2.100
で、redisサーバをインスールして、
https://github.com/gomodule/redigoを使うという手もありますが、
のサンプルプログラムで簡単に試すことができます。