使用Go语言和Elasticsearch构建简易的职位搜索后端
使用 Go 語言結合 Elasticsearch 架設一個簡易求職網站的後端。
本文是 MyNavi Advent Calendar 2021 的第9篇文章。
本次文章的源代码已在GitHub上公开。
文件夹的结构如下所示。
.
├── README.md
├── batch
│ ├── LoadData.go
│ ├── go.mod
│ ├── go.sum
│ └── test_data.xml
├── docker-compose.yml
├── es
│ ├── dic
│ │ └── test_dic.csv
│ ├── Dockerfile
│ ├── script
│ │ └── es_init.sh
│ └── sudachi
│ └── sudachi.json
├── search_api
│ ├── Dockerfile
│ ├── go.mod
│ ├── go.sum
│ ├── hr_api
│ ├── internal
│ │ ├── connect_es.go
│ │ ├── hr_query.go
│ │ └── hr_search.go
│ └── main.go
首先
用 Go 语言和 Elasticsearch 构建简易求职网站的后端。
在Docker容器上运行搜索引擎(Elasticsearch),然后从Go编写的Web服务器(echo)发送单词搜索查询。一旦进行单词搜索,我们将构建一个简单的职位搜索后端,以JSON格式输出职位信息。
构建时假设您发出以下查询来进行搜索。 shí nǐ xià de .)
#東京都の"カフェ"の求人を検索する
http://localhost:5000/search?keyword=カフェ&state=東京都
#東京都の"Go言語"の求人を検索する
http://localhost:5000/search?keyword=Go言語&state=東京都
#神奈川県の"アルバイト・パート"の求人を検索する
http://localhost:5000/search?keyword=アルバイト・パート&state=神奈川県
#求人のユニークidから検索する
http://localhost:5000/search?id=test
关键词用来指定搜索词,状态用来指定位置,id用作查询参数来指定招聘的唯一编号。
使用Kibana作为Elasticsearch的仪表盘。在Kibana中查看Elasticsearch的数据如下所示。
此外,我们还会创建一个批处理程序,将大量的XML数据转换成JSON格式,并将其输入到ElasticSearch中。
预计在生产环境中,我们每天运行一次该批处理程序以更新职位数据。
本次我们将简单地使用Go语言将大约10万条数据批量插入到ElasticSearch中。

操作系统是Ubuntu 20.04。
以下是工具的版本。
以下是制作步骤,请根据我记住的范围写下来,可能会有些前后顺序错乱,但希望您能参考一下。
1. 建立Elasticsearch
首先,我们建立一个Elasticsearch。
当查看 Elasticsearch 的版本公式时,发现有多个版本可供选择。
截至 2021 年 12 月 7 日,最新版本为 7.14.2。
选择7.8.1的理由有几个。最主要的原因是我们想要将Sudachi用作Elasticsearch的词典。在WorksApplication的存储库中,他们只支持到7.4,因此我们选择了7.4.1。
如果不一定非要使用辞书的话,我认为其他版本也可以运行。
最終的使用 Sudachi 的 Elasticsearch 的 Dockerfile 就是这样的。
由于不太了解 Docker 容器的最佳创建方式,可能对您并不有用。
我一直在尝试摸索如何减小容器镜像的大小,但最终并没有太大的改变,让我很苦恼。
ARG ELASTIC_VER=7.8.1
ARG SUDACHI_PLUGIN_VER=2.0.3
FROM ibmjava:8-jre-alpine as dict_builder
ARG ELASTIC_VER
ARG SUDACHI_PLUGIN_VER
WORKDIR /home
RUN wget https://github.com/WorksApplications/Elasticsearch-sudachi/releases/download/v${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}/analysis-sudachi-${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}.zip && \
unzip analysis-sudachi-${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}.zip && \
wget http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/sudachi-dictionary-20210802-core.zip && \
unzip sudachi-dictionary-20210802-core.zip && \
mkdir -p /usr/share/Elasticsearch/config/sudachi/ && \
mv sudachi-dictionary-20210802/system_core.dic /usr/share/Elasticsearch/config/sudachi/ && \
rm -rf sudachi-dictionary-20210802-core.zip sudachi-dictionary-20210802/
FROM docker.elastic.co/Elasticsearch/Elasticsearch:${ELASTIC_VER}
ARG ELASTIC_VER
ARG SUDACHI_PLUGIN_VER
COPY es/sudachi/sudachi.json /usr/share/Elasticsearch/config/sudachi/
COPY --from=dict_builder /home/analysis-sudachi-${ELASTIC_VER}-${SUDACHI_PLUGIN_VER}.zip /usr/share/Elasticsearch/
可以从docker-compose.yml文件中提取出Elasticsearch部分,大致如下。
如果您能查看本文的GitHub源代码,将不胜感激。
Elasticsearch:
build:
context: .
dockerfile: es/dockerfile
container_name: Elasticsearch
volumes:
- es-data:/usr/share/Elasticsearch/data
networks:
- Elasticsearch
ports:
- 9200:9200
environment:
- discovery.type=single-node
- node.name=Elasticsearch
- cluster.name=go-Elasticsearch-docker-cluster
- bootstrap.memory_lock=true
- ES_JAVA_OPTS=-Xms256m -Xmx256m
ulimits:
{ nofile: { soft: 65535, hard: 65535 }, memlock: { soft: -1, hard: -1 } }
healthcheck:
test: curl --head --max-time 120 --retry 120 --retry-delay 1 --show-error --silent http://localhost:9200
我在docker-compose.yml文件中注意到的问题是确保后端服务器可以进行搜索,并处理内存限制的相关问题。
稍后我会稍微提一下,在进行BulkInsert时遇到了一些问题。
请参考官方文件以获取详细信息。
构建Kibana
因为按照官方文档进行了Kibana的构建,所以它能够正常运行,我没有遇到任何困扰。
最后,docker-compose.yml变成了以下的样子。
kibana:
container_name: kibana
image: docker.elastic.co/kibana/kibana:7.8.1
depends_on: ["Elasticsearch"]
networks:
- Elasticsearch
ports:
- 5601:5601
environment:
- Elasticsearch_HOSTS=http://Elasticsearch:9200
- KIBANA_LOGGING_QUIET=true
healthcheck:
test: curl --max-time 120 --retry 120 --retry-delay 1 --show-error --silent http://localhost:5601
将数据插入到 Elasticsearch 数据库中。
我曾经在这里为了通过 Go 与 Elasticsearch 进行通信而苦恼。
首先,我纠结于选择使用哪个软件包。
一般来说,我认为以下两个软件包的使用较为广泛。
-
- https://github.com/olivere/elastic (弹性搜索)
- https://github.com/elastic/go-Elasticsearch (弹性搜索的 GO 语言版本)
1是Go语言中最受欢迎且Star数量最多的Elasticsearch客户端包。非常易用且文档齐全。一开始我们就打算使用这个来构建。
因为 Elastic 是官方提供的包,所以我决定这次使用2来创建。
由于文档并不是很完善,我参考了 GitHub 官方的_example 来创建。
一开始确实比较费力,但是熟悉之后发现有很多非常方便的功能。
这是一个需要花时间去适应的包。
批量插入
考虑到本次计划在 Elasticsearch 中输入 30 万条招聘数据,我之前创建时就假设需要使用 BulkInsert(go-Elasticsearch 中称之为 BulkIndex)来完成。
首先,我們使用普通的插入方法進行了創建。
當提取 Go 語言代碼時,大致如下。
req := esapi.IndexRequest{
Index: "baito",
DocumentID: string(j.Referencenumber),
Body: strings.NewReader(string(jobbody)),
Refresh: "true",
}
我根据GitHub上的公式参考进行了创建,但将大约10万条记录插入花费了大约1小时30分钟(由于遗失了测量照片)。
通常情况下,由于无法承受所有的插入,很容易在中途超时,因此我认为使用普通的插入方式无法实际应用30万条记录。
所以,我参考了BulkInsert,但是不太懂…(*´-ω・)ン? (。´-_・)ン? (´・ω・`)モキュ?
最终,我花了三天时间来理解这个文档(以及XML解析文档)。
最终完成的 Go 语言代码如下所示。
package main
import (
"bytes"
"encoding/json"
"encoding/xml"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/elastic/go-Elasticsearch/v7"
"github.com/elastic/go-Elasticsearch/v7/esapi"
"github.com/joho/godotenv"
)
type Job struct {
Referencenumber string `xml:"referencenumber" json:"referencenumber,string"`
Date string `xml:"date" json:"date,string"`
Url string `xml:"url" json:"url,string"`
Title string `xml:"title" json:"title,string"`
Description string `xml:"description" json:"description,string"`
State string `xml:"state" json:"state,string"`
City string `xml:"city" json:"city,string"`
Country string `xml:"country" json:"country,string"`
Station string `xml:"station" json:"station,string"`
Jobtype string `xml:"jobtype" json:"jobtype,string"`
Salary string `xml:"salary" json:"salary,string"`
Category string `xml:"category" json:"category,string"`
ImageUrls string `xml:"imageUrls" json:"imageurls,string"`
Timeshift string `xml:"timeshift" json:"timeshift,string"`
Subwayaccess string `xml:"subwayaccess" json:"subwayaccess,string"`
Keywords string `xml:"keywords" json:"keywords,string"`
}
var (
_ = fmt.Print
count int
batch int
)
func init() {
flag.IntVar(&count, "count", 300000, "Number of documents to generate")
flag.IntVar(&batch, "batch", 1000, "Number of documents to send in one batch")
flag.Parse()
rand.Seed(time.Now().UnixNano())
}
func main() {
log.SetFlags(0)
type bulkResponse struct {
Errors bool `json:"errors"`
Items []struct {
Index struct {
ID string `json:"_id"`
Result string `json:"result"`
Status int `json:"status"`
Error struct {
Type string `json:"type"`
Reason string `json:"reason"`
Cause struct {
Type string `json:"type"`
Reason string `json:"reason"`
} `json:"caused_by"`
} `json:"error"`
} `json:"index"`
} `json:"items"`
}
var (
buf bytes.Buffer
res *esapi.Response
err error
raw map[string]interface{}
blk *bulkResponse
jobs []*Job
indexName = "baito"
numItems int
numErrors int
numIndexed int
numBatches int
currBatch int
)
log.Printf(
"\x1b[1mBulk\x1b[0m: documents [%s] batch size [%s]",
humanize.Comma(int64(count)), humanize.Comma(int64(batch)))
log.Println(strings.Repeat("▁", 65))
// Create the Elasticsearch client
//
es, err := Elasticsearch.NewDefaultClient()
if err != nil {
log.Fatalf("Error creating the client: %s", err)
}
err = godotenv.Load(".env")
if err != nil {
log.Fatal("Error loading .env file")
}
xml_path := os.Getenv("BAITO_XML_PATH")
f, err := os.Open(xml_path)
if err != nil {
log.Fatal(err)
}
defer f.Close()
d := xml.NewDecoder(f)
for i := 1; i < count+1; i++ {
t, tokenErr := d.Token()
if tokenErr != nil {
if tokenErr == io.EOF {
break
}
// handle error somehow
log.Fatalf("Error decoding token: %s", tokenErr)
}
switch ty := t.(type) {
case xml.StartElement:
if ty.Name.Local == "job" {
// If this is a start element named "location", parse this element
// fully.
var job Job
if err = d.DecodeElement(&job, &ty); err != nil {
log.Fatalf("Error decoding item: %s", err)
} else {
jobs = append(jobs, &job)
}
}
default:
}
// fmt.Println("count =", count)
}
log.Printf("→ Generated %s articles", humanize.Comma(int64(len(jobs))))
fmt.Print("→ Sending batch ")
// Re-create the index
//
if res, err = es.Indices.Delete([]string{indexName}); err != nil {
log.Fatalf("Cannot delete index: %s", err)
}
res, err = es.Indices.Create(indexName)
if err != nil {
log.Fatalf("Cannot create index: %s", err)
}
if res.IsError() {
log.Fatalf("Cannot create index: %s", res)
}
if count%batch == 0 {
numBatches = (count / batch)
} else {
numBatches = (count / batch) + 1
}
start := time.Now().UTC()
// Loop over the collection
//
for i, a := range jobs {
numItems++
currBatch = i / batch
if i == count-1 {
currBatch++
}
// Prepare the metadata payload
//
meta := []byte(fmt.Sprintf(`{ "index" : { "_id" : "%d" } }%s`, a.Referencenumber, "\n"))
// fmt.Printf("%s", meta) // <-- Uncomment to see the payload
// Prepare the data payload: encode article to JSON
//
data, err := json.Marshal(a)
if err != nil {
log.Fatalf("Cannot encode article %d: %s", a.Referencenumber, err)
}
// Append newline to the data payload
//
data = append(data, "\n"...) // <-- Comment out to trigger failure for batch
// fmt.Printf("%s", data) // <-- Uncomment to see the payload
// // Uncomment next block to trigger indexing errors -->
// if a.ID == 11 || a.ID == 101 {
// data = []byte(`{"published" : "INCORRECT"}` + "\n")
// }
// // <--------------------------------------------------
// Append payloads to the buffer (ignoring write errors)
//
buf.Grow(len(meta) + len(data))
buf.Write(meta)
buf.Write(data)
// When a threshold is reached, execute the Bulk() request with body from buffer
//
if i > 0 && i%batch == 0 || i == count-1 {
fmt.Printf("[%d/%d] ", currBatch, numBatches)
res, err = es.Bulk(bytes.NewReader(buf.Bytes()), es.Bulk.WithIndex(indexName))
if err != nil {
log.Fatalf("Failure indexing batch %d: %s", currBatch, err)
}
// If the whole request failed, print error and mark all documents as failed
//
if res.IsError() {
numErrors += numItems
if err := json.NewDecoder(res.Body).Decode(&raw); err != nil {
log.Fatalf("Failure to to parse response body: %s", err)
} else {
log.Printf(" Error: [%d] %s: %s",
res.StatusCode,
raw["error"].(map[string]interface{})["type"],
raw["error"].(map[string]interface{})["reason"],
)
}
// A successful response might still contain errors for particular documents...
//
} else {
if err := json.NewDecoder(res.Body).Decode(&blk); err != nil {
log.Fatalf("Failure to to parse response body: %s", err)
} else {
for _, d := range blk.Items {
// ... so for any HTTP status above 201 ...
//
if d.Index.Status > 201 {
// ... increment the error counter ...
//
numErrors++
// ... and print the response status and error information ...
log.Printf(" Error: [%d]: %s: %s: %s: %s",
d.Index.Status,
d.Index.Error.Type,
d.Index.Error.Reason,
d.Index.Error.Cause.Type,
d.Index.Error.Cause.Reason,
)
} else {
// ... otherwise increase the success counter.
//
numIndexed++
}
}
}
}
// Close the response body, to prevent reaching the limit for goroutines or file handles
//
res.Body.Close()
// Reset the buffer and items counter
//
buf.Reset()
numItems = 0
}
}
// Report the results: number of indexed docs, number of errors, duration, indexing rate
//
fmt.Print("\n")
log.Println(strings.Repeat("▔", 65))
dur := time.Since(start)
if numErrors > 0 {
log.Fatalf(
"Indexed [%s] documents with [%s] errors in %s (%s docs/sec)",
humanize.Comma(int64(numIndexed)),
humanize.Comma(int64(numErrors)),
dur.Truncate(time.Millisecond),
humanize.Comma(int64(1000.0/float64(dur/time.Millisecond)*float64(numIndexed))),
)
} else {
log.Printf(
"Sucessfuly indexed [%s] documents in %s (%s docs/sec)",
humanize.Comma(int64(numIndexed)),
dur.Truncate(time.Millisecond),
humanize.Comma(int64(1000.0/float64(dur/time.Millisecond)*float64(numIndexed))),
)
}
}
我根据BulkInsert官方示例进行了研究,并创建了这个。
另外,由于我的电脑配置不算太高(4GB 内存,2个核心的CPU),因此XML解析器也必须要节省内存才行。
顺便提一句,写这段 Go 语言代码之前,我参考了 Python 的代码。到了一半的时候,我甚至考虑放弃 Go 语言。
从摘录到的内容来看,大致是这个样子。
for job in jobs:
index = job.as_dict()
if job.description == "" or job.description == null:
continue
bulk_file += json.dumps(
{"index": {"_index": index_name, "_type": "_doc", "_id": id}}
)
# The optional_document portion of the bulk file
bulk_file += "\n" + json.dumps(index) + "\n"
if id % 1000 == 0:
response = client.bulk(bulk_file)
bulk_file = ""
id += 1
continue
id += 1
if bulk_file != "":
response = client.bulk(bulk_file)
我們將語言代碼和 Python 代碼分批進行 BulkInsert,每次處理 1000 條。

使用Go语言的代码,我们能够在约3分钟内将大约14万条数据插入到Elasticsearch。
4. 创建一个后端服务器
然后,我创建了从Go的Web服务器向Elasticsearch进行搜索的部分。
(在中途放弃了BulkInsert,先处理这个部分)。
虽然有很多 Go 的 Web 服务器可选择,但我们选择了简单的 echo。
它的文档也非常丰富,我们能够简洁地编写代码。
文件夹的结构如下所示。
.
├── search_api
│ ├── Dockerfile
│ ├── go.mod
│ ├── go.sum
│ ├── hr_api
│ ├── internal
│ │ ├── connect_es.go
│ │ ├── hr_query.go
│ │ └── hr_search.go
│ └── main.go
main.go只是一个简单的程序,使用echo框架搭建了一个Web服务器。
package main
import (
internal "hr_api/internal"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
e.GET("/search", internal.HRSearch)
e.Logger.Fatal(e.Start(":5000"))
}
内部的下属团队为此事经历了一番头痛后进行了创建。具体包括如何组织结构体(最终决定全部使用字符串;;;)以及如何与Elasticsearch进行通信等。
package internal
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/labstack/echo"
)
type Query struct {
Keyword string `query:"keyword"`
State string `query:"state"`
Id string `query:"id"`
}
type Result struct {
Referencenumber string `xml:"referencenumber" json:"referencenumber,string"`
Date string `xml:"date" json:"date,string"`
Url string `xml:"url" json:"url,string"`
Title string `xml:"title" json:"title,string"`
Description string `xml:"description" json:"description,string"`
State string `xml:"state" json:"state,string"`
City string `xml:"city" json:"city,string"`
Country string `xml:"country" json:"country,string"`
Station string `xml:"station" json:"station,string"`
Jobtype string `xml:"jobtype" json:"jobtype,string"`
Salary string `xml:"salary" json:"salary,string"`
Category string `xml:"category" json:"category,string"`
ImageUrls string `xml:"imageUrls" json:"imageurls,string"`
Timeshift string `xml:"timeshift" json:"timeshift,string"`
Subwayaccess string `xml:"subwayaccess" json:"subwayaccess,string"`
Keywords string `xml:"keywords" json:"keywords,string"`
}
type Response struct {
Message string `json:"message"`
Results []Result
}
func HRSearch(c echo.Context) (err error) {
// クライアントからのパラメーターを取得
q := new(Query)
if err = c.Bind(q); err != nil {
return
}
res := new(Response)
var (
b map[string]interface{}
buf bytes.Buffer
)
// Elasticsearch へのクエリを作成
query := CreateQuery(q)
json.NewEncoder(&buf).Encode(query)
fmt.Printf(buf.String())
// Elasticsearch へ接続
es, err := ConnectElasticsearch()
if err != nil {
c.Error(err)
}
// Elasticsearch へクエリ
r, err := es.Search(
es.Search.WithContext(context.Background()),
es.Search.WithIndex("baito"),
es.Search.WithBody(&buf),
es.Search.WithTrackTotalHits(true),
es.Search.WithPretty(),
)
if err != nil {
c.Error(err)
}
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
c.Error(err)
}
// クエリの結果を Responce.Results に格納
for _, hit := range b["hits"].(map[string]interface{})["hits"].([]interface{}) {
result := new(Result)
doc := hit.(map[string]interface{})
fmt.Printf(result.Title)
result.Referencenumber = doc["_source"].(map[string]interface{})["referencenumber"].(string)
result.Date = doc["_source"].(map[string]interface{})["date"].(string)
result.Url = doc["_source"].(map[string]interface{})["url"].(string)
result.Title = doc["_source"].(map[string]interface{})["title"].(string)
result.State = doc["_source"].(map[string]interface{})["state"].(string)
result.Category = doc["_source"].(map[string]interface{})["category"].(string)
result.Description = doc["_source"].(map[string]interface{})["description"].(string)
result.City = doc["_source"].(map[string]interface{})["city"].(string)
result.Country = doc["_source"].(map[string]interface{})["country"].(string)
result.Station = doc["_source"].(map[string]interface{})["station"].(string)
result.Jobtype = doc["_source"].(map[string]interface{})["jobtype"].(string)
result.Salary = doc["_source"].(map[string]interface{})["salary"].(string)
result.ImageUrls = doc["_source"].(map[string]interface{})["imageurls"].(string)
result.Timeshift = doc["_source"].(map[string]interface{})["timeshift"].(string)
result.Subwayaccess = doc["_source"].(map[string]interface{})["subwayaccess"].(string)
result.Keywords = doc["_source"].(map[string]interface{})["keywords"].(string)
res.Results = append(res.Results, *result)
}
res.Message = "検索に成功しました。"
return c.JSON(http.StatusOK, res)
}
我认为,hr_query.go 是最应该考虑的部分。
我认为,通过给搜索赋予权重,可以大幅改善用户体验的部分。
package internal
func CreateQuery(q *Query) map[string]interface{} {
query := map[string]interface{}{}
if q.Id != "" {
query = map[string]interface{}{
"query": map[string]interface{}{
"bool": map[string]interface{}{
"must": []map[string]interface{}{
{
"match": map[string]interface{}{
"referencenumber": q.Id,
},
},
},
},
},
}
} else if q.Keyword != "" && q.State != "" {
query = map[string]interface{}{
"query": map[string]interface{}{
"bool": map[string]interface{}{
"must": []map[string]interface{}{
{
"bool": map[string]interface{}{
"should": []map[string]interface{}{
{
"match": map[string]interface{}{
"title": map[string]interface{}{
"query": q.Keyword,
"boost": 3,
},
},
},
{
"match": map[string]interface{}{
"description": map[string]interface{}{
"query": q.Keyword,
"boost": 2,
},
},
},
{
"match": map[string]interface{}{
"category": map[string]interface{}{
"query": q.Keyword,
"boost": 1,
},
},
},
},
"minimum_should_match": 1,
},
},
{
"bool": map[string]interface{}{
"must": []map[string]interface{}{
{
"match": map[string]interface{}{
"state": q.State,
},
},
},
},
},
},
},
},
}
} else if q.Keyword != "" && q.State == "" {
query = map[string]interface{}{
"query": map[string]interface{}{
"bool": map[string]interface{}{
"should": []map[string]interface{}{
{
"match": map[string]interface{}{
"title": map[string]interface{}{
"query": q.Keyword,
"boost": 3,
},
},
},
{
"match": map[string]interface{}{
"description": map[string]interface{}{
"query": q.Keyword,
"boost": 2,
},
},
},
{
"match": map[string]interface{}{
"category": map[string]interface{}{
"query": q.Keyword,
"boost": 1,
},
},
},
},
"minimum_should_match": 1,
},
},
}
} else if q.Keyword == "" && q.State != "" {
query = map[string]interface{}{
"query": map[string]interface{}{
"bool": map[string]interface{}{
"must": []map[string]interface{}{
{
"match": map[string]interface{}{
"state": q.State,
},
},
},
},
},
}
}
return query
}
这是连接到Elasticsearch并进行通信的部分。在这里,我想参考了在Qiita等网站上发布的文章,但是无法找到文章的URL了。。。
package internal
import (
"os"
Elasticsearch "github.com/elastic/go-Elasticsearch/v7"
)
func ConnectElasticsearch() (*Elasticsearch.Client, error) {
// 環境変数 ES_ADDRESS がある場合は記述されているアドレスに接続
// ない場合は、 http://localhost:9200 に接続
var addr string
if os.Getenv("ES_ADDRESS") != "" {
addr = os.Getenv("ES_ADDRESS")
} else {
addr = "http://localhost:9200"
}
cfg := Elasticsearch.Config{
Addresses: []string{
addr,
},
}
es, err := Elasticsearch.NewClient(cfg)
return es, err
}
5. 我们试着在浏览器中进行确认。 .)
这样,它终于能够运转了。
如果你运行 “docker-compose up” 并执行 “go run main.go”,我想你可以从浏览器中进行确认。
我认为在 VSCode 中进行操作会更容易理解。
如果你是通过 Remote SSH 在开发环境服务器上操作的,请参考上级的说明。
你可以通过浏览器来确认这个样子。
http://localhost:5000/search?keyword=カフェ&state=東京都
{
"message": "検索に成功しました。",
"Results": [
{
"referencenumber": "test",
"date": "2222-11-01",
"url": "test",
"title": "おしゃれカフェ・店舗スタッフ/ブック&カフェ/アルバイト・パート/おしゃれカフェ",
"description": "【省略】",
"state": "東京都",
"city": "渋谷区",
"country": "日本",
"station": "山手線渋谷駅 徒歩700分",
"jobtype": "アルバイト・パート",
"salary": "test円",
"category": "飲食・フード×おしゃれカフェ",
"imageurls": "test",
"timeshift": "週3日以上/1日3時間以上",
"subwayaccess": "山手線渋谷駅徒歩700分",
"keywords": "test"
},
6. 最后
我们在マイナビ运营着许多求职网站。通过使用Go语言创建简易的求职网站,我能够重新学习技术背景。
如果你有兴趣,请一定试着创建!