Azure Communication ServicesのSDK(Software Development Kit)のJavaを試してみる

natgeo.nikkeibp.co.jp

 P. キアネセンスとP. アレニーには、幻覚を引き起こす化合物であるサイロシビン(シロシビン) が含まれている。サイロシビンを含むシビレタケ属のキノコは数百種にのぼり、最近では、こうしたキノコには心的外傷後ストレス障害PTSD)やうつ病の治療に有効な可能性があるとして注目が集まっている。米国では合法化の支持が広がり、精神疾患に対する治療効果を示す証拠も積み上がりつつある(編注:日本では麻薬原料植物として規制)。

米国で幻覚キノコ狩りが静かな人気、都会の庭や植え込みに自生 | ナショナルジオグラフィック日本版サイト

⇧ 流石は、ステロイド大国合衆国、大麻に続き、マジックマッシュルームまで合法化を進めてるとは。

Wikipediaの情報によると、

マジックマッシュルーム(Magic mushroom, shroom)は、トリプタミンアルカロイドシロシビンシロシンを含んだ、菌類キノコの俗称。種は200以上存在し、世界中に広く自生している。毒キノコだが、主に幻覚作用であり重症や死亡はまずない。日本では『今昔物語』にて古代の呼び方で舞茸(今でいうマイタケとは違う)とされており、後世にも笑茸(わらいたけ)、踊茸などと言及された。多くが20世紀に菌類として同定され、大半はシビレタケ属 Psilocybe や糞を好むヒカゲタケ属 Panaeolus に属し、具体的な種はワライタケオオワライタケヒカゲシビレタケ(日本原産)、ミナミシビレタケ (Psilocybe cubensis)、リバティーキャップ英語版 (Psilocybe Semilanceata) など。

マジックマッシュルーム - Wikipedia

日本では2002年から、シロシビンを含有するきのこ類を故意に使用・所持することは麻薬及び向精神薬取締法によって規制され、もっぱら鑑賞用となるアメリカや日本では胞子の所持は合法である。21世紀に入り、成分シロシビンによるうつ病や薬物依存症の治療研究が注目され、日本でも強迫性障害に対するヒカゲシビレタケ抽出物が基礎研究された。

マジックマッシュルーム - Wikipedia

⇧ 日本でも、2002年までは、規制されてなかったんですね、いまは2022年だから、20年前か。研究結果次第では、医療用として規制緩和される日が来るんですかね?

総務省の資料によると、日本は世界でも自殺大国という位置付けらしいのですが...

世界保健機関(WHO:World Health Organization)が公表した資料(以下「WHO資料」という。)に基づき、諸外国における自殺者の状況をみると、年間自殺者数については、インドが 12 万 7,151 人と最も多く、次いでロシア4万 2,855 人、アメリカ3万 2,559 人の順となっており、我が国は3万 707 人で4位となっている。また、自殺死亡率を比較すると、リトアニアが 34.1 と最も高く、次いで韓国 31.0、ロシア 30.1、ベラルーシ 27.4、ガイアナ 26.4、カザフスタン 25.6、ハンガリー24.6 の順となっており、我が国は 24.4 で8位となっている(注)。
(注)WHO資料においては、年間自殺者数及び自殺死亡率のデータについて、国により異なる年のデータが使用されており、我が国の年間自殺者数及び自殺死亡率については、厚生労働省の「平成 21 年人口動態統計」の数値が用いられている。 

https://www.soumu.go.jp/main_content/000164590.pdf 

自殺状況資料によれば、平成 23 年の自殺者数3万 651 人のうち、自殺の原因・動機が特定された者は2万 2,581 人(73.7%)、特定されなかった者は 8,070 人(26.3%)となっている。
自殺の原因・動機が特定された2万 2,581 人について、原因・動機別にみると、健康問題が1万 4,621 人(64.7%)と最も多く、次いで経済・生活問題が 6,406 人(28.4%)、家庭問題が 4,547 人(20.1%)、勤務問題が 2,689人(11.9%)などとなっており、健康問題が6割以上を占めている(注2)。
また、この原因・動機別の自殺者数の順位は、表9のとおり、平成 10 年に自殺者が急増してからほぼ同様の傾向で推移している。
(注2)警察庁の自殺統計原票は、遺書等の自殺を裏付ける資料により明らかに推定できる原因・動機を3つまで計上することとされているため、原因・動機別の自殺者数の合計は原因・動機特定者数(2万 2,581 人)とは一致しない。

https://www.soumu.go.jp/main_content/000164590.pdf 

⇧ 健康問題が最も多いようですが、具体的なことは記載されてないので何とも言えないですが、精神的な影響も大きい気がしますかね、現代社会は未曾有の大ストレス社会と言っても過言ではない気がしますし...

冒頭から脱線しましたが、今回もAzureのサービスについてです。

「Azureアカウント」の作成なんかについては、

ts0818.hatenablog.com

⇧ 前回の記事で行っています。

レッツトライ~。

Azure Communication ServicesのSDK(Software Development Kit)のJavaを利用するのに必要なもの

公式のドキュメントによると、

docs.microsoft.com

Prerequisites

https://docs.microsoft.com/en-us/java/api/overview/azure/communication-common-readme?view=azure-java-stable

⇧ とあるのだけど、ビルドツールとして「Maven」以外を使うってことを考慮してくれてないのがよく分からん...

事前に、必要な要件が上記ということらしい。

「Azureアカウント」は作成済み、JDKEclipse内蔵の使ってるし、ビルドツールは「Gradle」使ってるので、「Azure Commnunication Services 」のリソース作成が必要ってことになるのかな。

Azure Communication Servicesのリソース作成で

というわけで、「Azure Commnunication Services 」のリソースを作成で。

Azure Portalで「すべてのサービス」を選択。

「communication」的な用語で検索して、ヒットする「通信サービス」を選択。

「リソース の作成」ボタンを押下。

「基本情報」タブの入力が終わったら、「レビューと作成」タブを選択。「タグ」については割愛。

「作成」ボタンを押下。

「Azure Commnunication Services 」のリソースが作成されます。

「リソースに移動」ボタン押下。

確認できました。

Azure Communication Servicesを利用するのに必要な依存関係(Spring Boot使ってる場合)

Azure Communication Servicesを利用するのに必要な「依存関係」としては、

github.com

github.com

⇧ 上記の2つは最低限必要らしい。

ただ、手前味噌になり恐縮ですが、

ts0818.hatenablog.com

⇧ 上記記事で、「Azure Commnunication Services」について調査した時に、「Azure Commnunication Services」のAPIを利用するための認証とかの関係で、

github.com

⇧ 上記の「依存関係」も共通で必要になる気がするんだけど、Microsoftさんのドキュメントが相変わらずカオスなので、よく分からんです...

今回はチャットを試してみたいので、

github.com

⇧ 上記の「依存関係」は必須になると思うけど。

Azure Communication ServicesのSDK(Software Development Kit)のJavaを試してみる

というわけで、試してみます。

フロントエンドについては、

ts0818.hatenablog.com

⇧上記の記事で利用してたVue.jsのプロジェクトに追加してます。

サーバーサイドは新しく作ってます。

まずは、フロントエンド側。

⇧ 新規で追加したのは、3ファイル。

■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-router-work\my-project-vue\src\modules\chat\chat.user.model.ts

interface ChatUserModelType {
  userId: string;
  userName: string;
  messageType: string;
  content: string;
  chatMessageId: any;
  chatThreadId: any;
}

class ChatUserModel implements ChatUserModelType {
  userId = "";
  userName = "";
  messageType = "";
  content = "";
  chatMessageId = "";
  chatThreadId = "";

  constructor(init?: Partial<chatusermodel>) {
    Object.assign(this, init);
  }
}
export default new ChatUserModel();

■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-router-work\my-project-vue\src\api\chatService.ts

import request from "@/api/request";

class ChatService {
  sendMessage(data: any) {
    return request({
      method: "post",
      url: "/chat/message",
      data: data,
      responseType: "json",
    });
  }
}
export default new ChatService();

■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-router-work\my-project-vue\src\views\chat\TestChat.vue

<template>
  <div id="chat">
    <div id="chat-message">
      <ul v-if="messageList && messageList.length">
        <li v-for="item in messageList" :key="item.userId">
          <div>
            <span> {{ item.content }}</span>
            <span> {{ item.chatThreadId }} </span>
            <span> {{ item.chatMessageId }} </span>
          </div>
        </li>
      </ul>
    </div>
    <div id="chat-form">
      <el-form :model="formDataModel" ref="formDataModel">
        <el-form-item label="メッセージ">
          <el-input type="textarea" v-model="formDataModel.content"></el-input>
        </el-form-item>
        <button @click.prevent="sendMessage">送信</button>
      </el-form>
    </div>
  </div>
</template>

<script lang="ts">
/* eslint-disable no-console */
import { Component, Vue, Watch } from "vue-property-decorator";
import ChatUserModel from "@/modules/chat/chat.user.model";
import chatService from "@/api/chatService";

@Component
export default class TestChatView extends Vue {
  private formDataModel: any = {};
  private messageList: Array<typeof ChatUserModel> = [];
  private chatUserModel: any = {};

  created() {
    console.log("created");
    this.formDataModel = Object.assign({}, this.formDataModel, {
      userId: ChatUserModel.userId,
      userName: ChatUserModel.userName,
      messageType: ChatUserModel.messageType,
      content: ChatUserModel.content,
      chatMessageId: ChatUserModel.chatMessageId,
      chatThreadId: ChatUserModel.chatThreadId,
    });
    //this.messageList.push(this.formDataModel);
  }

  private async sendMessage() {
    //this.messageList.push(this.formDataModel);
    //let formData = new FormData();
    console.dir(this.formDataModel);
    //formData.append("chatUserDto", this.formDataModel);
    this.chatUserModel = Object.assign({}, this.formDataModel, {
      userId: this.formDataModel.userId,
      userName: this.formDataModel.userName,
      messageType: this.formDataModel.messageType,
      content: this.formDataModel.content,
      chatMessageId: this.formDataModel.chatMessageId,
      chatThreadId: this.formDataModel.chatThreadId,
    });
    console.dir(this.chatUserModel);
    this.$set(this.formDataModel, "content", "");
    try {
      await chatService.sendMessage(this.chatUserModel).then((result) => {
        console.log("await chatService.sendMessage");
        console.dir(result.data);
        this.messageList.push({
          userId: result.data.userId,
          userName: result.data.userName,
          messageType: result.data.messageType,
          content: result.data.content,
          chatMessageId: result.data.chatMessageId,
          chatThreadId: result.data.chatThreadId,
        });
      });
    } catch (error) {
      console.log(error);
    }
  }

  @Watch("formDataModel.content")
  private watchContent() {
    console.dir(this.formDataModel);
  }
}
</script>

■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-router-work\my-project-vue\src\router\index.ts

import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";
import HomeView from "../views/HomeView.vue";

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
  {
    path: "/",
    name: "home",
    component: HomeView,
  },
  {
    path: "/about",
    name: "about",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
  },
  // {
  //   path: "/test",
  //   name: "test",
  //   component: () => import("../views/NavigationGuard.vue"),
  // },
  // {
  //   path: "/testdom",
  //   name: "testdom",
  //   component: () => import("../views/TestDom.vue"),
  // },
  {
    path: "/testui",
    name: "testui",
    component: () => import("../views/TestElementUi.vue"),
  },
  {
    path: "/user/list",
    component: () => import("../views/user/list.vue"),
  },
  {
    path: "/user/edit/:id",
    name: "/user/edit",
    component: () => import("../views/user/edit/index.vue"),
    //props: (route) => ({ id: Number(route.params.id) }),
  },
  {
    path: "/user/confirm",
    name: "/user/confirm",
    component: () => import("../views/user/confirm/index.vue"),
    //props: (route) => ({ id: Number(route.params.id) }),
  },
  {
    path: "/chat",
    name: "chat",
    component: () => import("../views/chat/TestChat.vue"),
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

export default router;

⇧ で保存。その他は変更なし。

続きまして、サーバーサイド。

■azure-communication-services/build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.7'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'org.postgresql:postgresql'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
 
	// Azure Communication Services
	implementation 'com.azure:azure-communication-common:1.1.2'
	implementation 'com.azure:azure-sdk-bom:1.2.1'
	implementation 'com.azure:azure-communication-identity:1.1.8'
	implementation 'com.azure:azure-communication-chat:1.2.1'
}

tasks.named('test') {
	useJUnitPlatform()
}
    

■azure-communication-services/src/main/resources/application.properties

server.port=8088

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5434/test
spring.datasource.username=postgres
spring.datasource.password=postgres

azure.communication.endpoint=[Azure Communication Servicesのエンドポイント]
azure.communication.accessKey=[Azure Communication Servicesのアクセスキー]
azure.communication.connection=endpoint=[Azure Communication Servicesの接続文字列]
    

⇧ Azure Communication ServicesへのAPI経由での接続は、

  • エンドポイント+アクセスキー
  • 接続文字列

のどちらかでOK。「接続文字列」が「エンドポイント」と「アクセスキー」を合わせたものになっているため。一応、application.propertiesに両方記載してますが、今回は「接続文字列」は使用してません。

設定する値は、Azure Portalで「Azure Communication Services」のリソースの「キー」で確認できます。

■azure-communication-services/src/main/java/com/example/demo/azure/communication/filter/CORSFilter.java

package com.example.demo.azure.communication.filter;


import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;

@Component
public class CORSFilter implements Filter {
	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

	    HttpServletRequest request = (HttpServletRequest) req;
	    HttpServletResponse response = (HttpServletResponse) res;

	    response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
	    response.setHeader("Access-Control-Allow-Credentials", "true");
	    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
	    response.setHeader("Access-Control-Max-Age", "3600");
	    response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");

	    chain.doFilter(req, res);
	}

	@Override
	public void init(FilterConfig filterConfig) {
	}

	@Override
	public void destroy() {
	}

}
    

■azure-communication-services/src/main/java/com/example/demo/azure/communication/dto/ChatUserDto.java

package com.example.demo.azure.communication.dto;

import java.io.Serializable;
import java.util.List;

import com.azure.communication.chat.models.ChatMessageType;
import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.Data;

@Data
public class ChatUserDto implements Serializable {

	@JsonProperty("userId")
	private Long userId;
	@JsonProperty("userName")
	private String userName;
	//@JsonIgnore
	@JsonProperty("messageType")
	private ChatMessageType messageType;
	@JsonProperty("content")
	private String content;
	@JsonProperty("chatMessageId")
	private String chatMessageId;
	@JsonProperty("chatThreadId")
	private String chatThreadId;
	@JsonProperty("participantList")
	private List participantList;
}
    

■azure-communication-services/src/main/java/com/example/demo/azure/communication/auth/ApiAuth.java

package com.example.demo.azure.communication.auth;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.azure.communication.common.CommunicationUserIdentifier;
import com.azure.communication.identity.CommunicationIdentityClient;
import com.azure.communication.identity.CommunicationIdentityClientBuilder;
import com.azure.communication.identity.models.CommunicationTokenScope;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.AzureKeyCredential;

import lombok.Data;

@Service
@Data
public class ApiAuth {
	@Value("${azure.communication.endpoint}")
	private String endpoint;
	
	@Value("${azure.communication.accessKey}")
	private String accessKey;
	
	
	public CommunicationIdentityClient authCommunicationServicesByIdentity () {
		CommunicationIdentityClient communicationIdentityClient = new CommunicationIdentityClientBuilder()
		        .endpoint(endpoint)
		        .credential(new AzureKeyCredential(accessKey))
		        .buildClient();
		return communicationIdentityClient;
	}
	
	public CommunicationUserIdentifier createCommunicationServicesId (CommunicationIdentityClient client) {

		return client.createUser();
	}
	
	public AccessToken createAccessTokenForCommunicationServices(CommunicationIdentityClient client) {
		List<CommunicationTokenScope> scopes = new ArrayList<>(Arrays.asList(CommunicationTokenScope.CHAT));
		AccessToken accessToken = client.getToken(client.createUser(), scopes);
		return accessToken;
	}
	
	public AccessToken updateAccessTokenForCommunicationServices(CommunicationIdentityClient client, CommunicationUserIdentifier userIdentifier, List<CommunicationTokenScope>  scopes) {
		CommunicationUserIdentifier identity = new CommunicationUserIdentifier(userIdentifier.getId());
		AccessToken refreshToken = client.getToken(identity, scopes);
		return refreshToken;
	}
	
	public void revokeAccessToken(CommunicationIdentityClient client, CommunicationUserIdentifier userIdentifier) {
		client.revokeTokens(userIdentifier);
	}
	
	public void deleteAccessToken(CommunicationIdentityClient client, CommunicationUserIdentifier userIdentifier) {
		client.revokeTokens(userIdentifier);
	}	
	
}
 

■azure-communication-services/src/main/java/com/example/demo/azure/communication/service/CommunicationChatServiceImpl.java

package com.example.demo.azure.communication.service;

import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import com.azure.communication.chat.ChatClient;
import com.azure.communication.chat.ChatClientBuilder;
import com.azure.communication.chat.ChatThreadClient;
import com.azure.communication.chat.models.ChatMessageType;
import com.azure.communication.chat.models.ChatParticipant;
import com.azure.communication.chat.models.ChatThreadItem;
import com.azure.communication.chat.models.CreateChatThreadOptions;
import com.azure.communication.chat.models.CreateChatThreadResult;
import com.azure.communication.chat.models.SendChatMessageOptions;
import com.azure.communication.chat.models.SendChatMessageResult;
import com.azure.communication.common.CommunicationTokenCredential;
import com.azure.communication.common.CommunicationUserIdentifier;
import com.azure.communication.identity.CommunicationIdentityClient;
import com.azure.core.credential.AccessToken;
import com.azure.core.http.rest.PagedIterable;
import com.example.demo.azure.communication.auth.ApiAuth;
import com.example.demo.azure.communication.dto.ChatUserDto;

@Service
public class CommunicationChatServiceImpl {

	@Autowired
	private ApiAuth apiAuth;
	
	public CommunicationTokenCredential createUserCredential() {
		CommunicationIdentityClient client = this.apiAuth.authCommunicationServicesByIdentity();
		AccessToken userAccessToken = this.apiAuth.createAccessTokenForCommunicationServices(client);
		CommunicationTokenCredential userCredential = new CommunicationTokenCredential(userAccessToken.getToken());
		return userCredential;
	}
	
	public CreateChatThreadOptions createChatThreadOptions(ChatUserDto ChatUserDto) {
		CommunicationIdentityClient client = this.apiAuth.authCommunicationServicesByIdentity();
        CreateChatThreadOptions createChatThreadOptions = new CreateChatThreadOptions("Topic");
        
        if (CollectionUtils.isEmpty(ChatUserDto.getParticipantList())) {
        	String userId = null;
        	if (Objects.isNull(ChatUserDto.getUserId())) {
        		userId = client.createUser().getId();
        	} else {
        		userId = String.valueOf(ChatUserDto.getUserId());
        	}
        	ChatParticipant threadParticipant = new ChatParticipant()
        	        .setCommunicationIdentifier(new CommunicationUserIdentifier(userId))
        	        .setDisplayName("participant-" + userId);
	        createChatThreadOptions.addParticipant(threadParticipant);
        } else {
    		for (String participantId: ChatUserDto.getParticipantList()) {
    	        ChatParticipant threadParticipant = new ChatParticipant()
    	        .setCommunicationIdentifier(new CommunicationUserIdentifier(participantId))
    	        .setDisplayName("participant-" + participantId);

    	        createChatThreadOptions.addParticipant(threadParticipant);
    		}       	
        }
        return createChatThreadOptions;
	}
	
	public ChatClient  authCommunicationServicesByAcessToken() {
       
		CommunicationTokenCredential userCredential = this.createUserCredential();
		final ChatClientBuilder builder = new ChatClientBuilder();
        builder.endpoint(apiAuth.getEndpoint())
            .credential(userCredential);
        ChatClient chatClient = builder.buildClient();
        return chatClient;
	}
	
	public CreateChatThreadResult createChatThread(CreateChatThreadOptions createChatThreadOptions) {
		ChatClient chatClient = this.authCommunicationServicesByAcessToken();
		CreateChatThreadResult createChatThreadResult = chatClient.createChatThread(createChatThreadOptions);
		return createChatThreadResult;
	}
	
	public PagedIterable<ChatThreadItem> showAllChatThreads(ChatClient chatClient) {
		return chatClient.listChatThreads();
	}
	
	public ChatUserDto sendMessage(ChatUserDto chatUserDto, ChatClient chatClient) {
		SendChatMessageOptions sendChatMessageOptions = new SendChatMessageOptions()
				.setContent(chatUserDto.getContent())
				// .setType(Objects.isNull(chatUserDto.getMessageType())? ChatMessageType.TEXT:
				// chatUserDto.getMessageType())
				.setType(ChatMessageType.TEXT)
				.setSenderDisplayName(chatUserDto.getUserName());
        
        ChatThreadClient chatThreadClient = chatClient.getChatThreadClient(chatUserDto.getChatThreadId());
        
        SendChatMessageResult sendChatMessageResult = chatThreadClient.sendMessage(sendChatMessageOptions);
        String chatMessageId = sendChatMessageResult.getId();
        chatUserDto.setChatMessageId(chatMessageId);
        
        chatThreadClient.sendReadReceipt(chatMessageId);
        
        return chatUserDto;
	}
	
}
    

■azure-communication-services/src/main/java/com/example/demo/azure/communication/controller/ChatController.java

package com.example.demo.azure.communication.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.azure.communication.chat.ChatClient;
import com.azure.communication.chat.models.ChatThreadProperties;
import com.azure.communication.chat.models.CreateChatThreadOptions;
import com.azure.communication.chat.models.CreateChatThreadResult;
import com.example.demo.azure.communication.dto.ChatUserDto;
import com.example.demo.azure.communication.service.CommunicationChatServiceImpl;

import io.micrometer.core.instrument.util.StringUtils;

@RestController
@RequestMapping("/chat")
public class ChatController {

	@Autowired
	private CommunicationChatServiceImpl communicationChatServiceImpl;
	
	@PostMapping("/message")
	public ChatUserDto message(@RequestBody ChatUserDto chatUserDto ) {
		ChatClient chatClient = this.communicationChatServiceImpl.authCommunicationServicesByAcessToken();
		
		if (StringUtils.isEmpty(chatUserDto.getChatThreadId())) {
			//CommunicationTokenCredential communicationTokenCredential = this.communicationChatServiceImpl.createUserCredential();
			CreateChatThreadOptions createChatThreadOptions = this.communicationChatServiceImpl.createChatThreadOptions(chatUserDto);
			CreateChatThreadResult createChatThreadResult = chatClient.createChatThread(createChatThreadOptions);

			//CreateChatThreadResult createChatThreadResult = this.communicationChatServiceImpl.createChatThread(createChatThreadOptions);
			ChatThreadProperties chatThreadProperties = createChatThreadResult.getChatThread();
			chatUserDto.setChatThreadId(chatThreadProperties.getId());			
		}
		return communicationChatServiceImpl.sendMessage(chatUserDto, chatClient);
	}
}

■azure-communication-services/src/main/java/com/example/demo/AzureCommunicationServicesApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AzureCommunicationServicesApplication {

	public static void main(String[] args) {
		SpringApplication.run(AzureCommunicationServicesApplication.class, args);
	}

}    

⇧ 保存して、「フロントエンド」「サーバーサイド」のそれぞれのサーバーを起動。

ブラウザにアクセスし、Chatのページを開きます。

適当にメッセージを送信すると、メッセージが追加されていきます。

毎回、チャットスレッド作成してしまってるのを何とかしたい...何より、全くリアルタイム性が実現できずタイムラグが大きいのが致命的ですし...

データベースと連携できてなかったり、既読機能とかもできなかったり、他にも諸々できてないことが盛りだくさんになってますが、「Azure Communication Services」のSDK(Software Development Kit)のJavaを試すことができました。

相変わらず、Microsoftさんのドキュメント情報が足りてない...

おかげで、連休のほとんどが潰れたということは、言うまでもない...

今回はこのへんで。