Commit 7e99bd32 authored by limenglei01's avatar limenglei01

add douyincloud-gin-demo

parents
FROM public-cn-beijing.cr.volces.com/public/base:golang-1.17.1-alpine3.14 as builder
# 指定构建过程中的工作目录
WORKDIR /app
# 将当前目录(dockerfile所在目录)下所有文件都拷贝到工作目录下(.dockerignore中文件除外)
COPY . /app/
# 执行代码编译命令。操作系统参数为linux,编译后的二进制产物命名为main,并存放在当前目录下。
RUN GOPROXY=https://goproxy.cn,direct GOOS=linux GOARCH=amd64 go build -o main .
FROM public-cn-beijing.cr.volces.com/public/base:alpine-3.13
WORKDIR /opt/application
COPY --from=builder /app/main /app/run.sh /opt/application/
USER root
RUN chmod -R 777 /app/run.sh
CMD /opt/application/run.sh
Copyright (year) Bytedance Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
\ No newline at end of file
# douyincloud-gin-demo
本项目是抖音云平台基于go语言gin框架的开发模版,模版通过使用Redis和MongoDB实现了简单的hello-world功能。\
抖音云平台支持基于Git代码和Docker镜像部署两种方式。其中,Dockerfile文件可以参考本项目中的Dockerfile文件。
部署在抖音云平台的服务日志需要重定向到标准输出,并在抖音云平台日志功能中查看。
## 目录结构
~~~
.
├── Dockerfile Dockerfile文件
├── Readme.md Readme文件
├── component 组件目录
│ ├── mongo.go mongo组件
│ ├── redis.go redis组件
│ └── types.go 组件接口声明
├── go.mod go.mod文件
├── go.sum go.sum文件
├── main.go 主函数入口
├── run.sh 容器运行时启动文件
└── service 业务逻辑目录
└── service.go 业务逻辑文件
~~~
## 请求方法
前往抖音云托管平台「调试」功能界面,进行请求调试。
## API说明
### `GET /api/hello`
对组件打招呼
### 请求参数
- `target``string` 组件名:redis,mongodb
### 响应结果
```json
{
"err_no": 0,
"err_msg": "success",
"data": "hello,redis"
}
```
### `POST /api/set_name`
给组件设置名称
### 请求参数
- `target`:`string` 组件名:redis,mongodb
- `name`:`string` 名称
### 响应结果
```json
{
"err_no": 0,
"err_msg": "success",
"data": ""
}
```
### 组件使用注意事项
在抖音云托管平台上启用组件后,抖音云平台会自动将组件的地址,账号,密码以环境变量的方式注入到容器中。\
以Redis为例,在抖音云托管平台启用Redis组件后,平台会生成 `REDIS_ADDRESS``REDIS_USERNAME``REDIS_PASSWORD`三个环境变量,在业务代码中可以使用如下代码获取相应值。
```
redisAddr := os.Getenv("REDIS_ADDRESS")
redisUserName := os.Getenv("REDIS_USERNAME")
redisPassword := os.Getenv("REDIS_PASSWORD")
```
## License
This project is licensed under the [Apache-2.0 License](LICENSE).
/*
Copyright (year) Bytedance Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package component
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"net/url"
"os"
"time"
)
var (
// mongo地址
mongoAddr = ""
// mongo用户名
mongoUserName = ""
// mongo密码
mongoPassWord = ""
)
const collectionName = "demo"
type mongoComponent struct {
client *mongo.Client
dataBase string
}
type model struct {
Key string `bson:"key"` //类型
Value string `bson:"value"` //值
}
func (m *mongoComponent) GetName(ctx context.Context, key string) (name string, err error) {
coll := m.client.Database(m.dataBase).Collection(collectionName)
doc := &model{}
filter := bson.M{"key": key}
result := coll.FindOne(ctx, filter)
if err := result.Decode(doc); err != nil {
return "", err
}
return doc.Value, err
}
func (m *mongoComponent) SetName(ctx context.Context, key string, name string) error {
coll := m.client.Database(m.dataBase).Collection(collectionName)
filter := bson.M{"key": key}
update := bson.M{"$set": model{Key: key, Value: name}}
_, err := coll.UpdateMany(ctx, filter, update)
if err != nil {
return err
}
return nil
}
//NewMongoComponent 新建一个mongodbComponent,其实现了HelloWorldComponent接口
func NewMongoComponent() *mongoComponent {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
tmp, err := url.Parse(mongoAddr)
if err != nil {
panic("mongo addr parse error")
}
authSource := tmp.Query().Get("authSource")
credential := options.Credential{
AuthSource: authSource,
Username: mongoUserName,
Password: mongoPassWord,
}
mongoUrl := fmt.Sprintf("mongodb://%s", mongoAddr)
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoUrl).SetAuth(credential))
if err != nil {
fmt.Printf("mongoClient init error. err %s\n", err)
panic("mongo connect error")
}
return &mongoComponent{client, "demo"}
}
//init 项目启动时,会从环境变量中获取mongodb的地址,用户名和密码
func init() {
mongoAddr = os.Getenv("MONGO_ADDRESS")
mongoUserName = os.Getenv("MONGO_USERNAME")
mongoPassWord = os.Getenv("MONGO_PASSWORD")
}
/*
Copyright (year) Bytedance Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package component
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
"os"
)
var (
//Redis地址
redisAddr = ""
//Redis用户名
redisUserName = ""
//Redis密码
redisPassword = ""
)
type redisComponent struct {
client *redis.Client
}
func (r *redisComponent) GetName(ctx context.Context, key string) (name string, err error) {
return r.client.Get(ctx, key).Result()
}
func (r *redisComponent) SetName(ctx context.Context, key string, name string) error {
_, err := r.client.Set(ctx, key, name, 0).Result()
return err
}
//NewRedisComponent 初始化一个实现了HelloWorldComponent接口的RedisComponent
func NewRedisComponent() *redisComponent {
rdb := redis.NewClient(&redis.Options{
Addr: redisAddr,
Username: redisUserName,
Password: redisPassword,
DB: 0, // use default DB
})
_, err := rdb.Ping(context.TODO()).Result()
if err != nil {
fmt.Printf("redisClient init error. err %s", err)
panic(fmt.Sprintf("redis init failed. err %s\n", err))
}
return &redisComponent{
client: rdb,
}
}
//init 项目启动时会从环境变量中获取
func init() {
redisAddr = os.Getenv("REDIS_ADDRESS")
redisUserName = os.Getenv("REDIS_USERNAME")
redisPassword = os.Getenv("REDIS_PASSWORD")
}
/*
Copyright (year) Bytedance Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package component
import (
"context"
"fmt"
)
type HelloWorldComponent interface {
GetName(ctx context.Context, key string) (name string, err error)
SetName(ctx context.Context, key string, name string) error
}
const Mongo = "mongodb"
const Redis = "redis"
var (
mongoHelloWorld *mongoComponent
redisHelloWorld *redisComponent
)
//GetComponent 通过传入的component的名称返回实现了HelloWorldComponent接口的component
func GetComponent(component string) (HelloWorldComponent, error) {
switch component {
case Mongo:
return mongoHelloWorld, nil
case Redis:
return redisHelloWorld, nil
default:
return nil, fmt.Errorf("invalid component")
}
}
func InitComponents() {
mongoHelloWorld = NewMongoComponent()
redisHelloWorld = NewRedisComponent()
ctx := context.TODO()
err := mongoHelloWorld.SetName(ctx, "name", "mongodb")
if err != nil {
panic(err)
}
err = redisHelloWorld.SetName(ctx, "name", "redis")
if err != nil {
panic(err)
}
}
module douyincloud-gin-demo
go 1.18
require (
github.com/gin-gonic/gin v1.8.1
github.com/go-redis/redis/v8 v8.11.5
go.mongodb.org/mongo-driver v1.10.0
)
require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
This diff is collapsed.
/*
Copyright (year) Bytedance Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"douyincloud-gin-demo/component"
"douyincloud-gin-demo/service"
"github.com/gin-gonic/gin"
)
func main() {
component.InitComponents()
r := gin.Default()
r.GET("/api/hello", service.Hello)
r.POST("/api/set_name", service.SetName)
r.Run(":8000")
}
#!/bin/sh
cd /opt/application/ && ./main
\ No newline at end of file
/*
Copyright (year) Bytedance Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package service
import (
"douyincloud-gin-demo/component"
"fmt"
"github.com/gin-gonic/gin"
)
func Hello(ctx *gin.Context) {
target := ctx.Query("target")
if target == "" {
Failure(ctx, fmt.Errorf("param invalid"))
return
}
fmt.Printf("target= %s\n", target)
hello, err := component.GetComponent(target)
if err != nil {
Failure(ctx, fmt.Errorf("param invalid"))
return
}
name, err := hello.GetName(ctx, "name")
if err != nil {
Failure(ctx, err)
return
}
Success(ctx, name)
}
func SetName(ctx *gin.Context) {
var req SetNameReq
err := ctx.Bind(&req)
if err != nil {
Failure(ctx, err)
return
}
hello, err := component.GetComponent(req.Target)
if err != nil {
Failure(ctx, fmt.Errorf("param invalid"))
return
}
err = hello.SetName(ctx, "name", req.Name)
if err != nil {
Failure(ctx, err)
return
}
Success(ctx, "")
}
func Failure(ctx *gin.Context, err error) {
resp := &Resp{
ErrNo: -1,
ErrMsg: err.Error(),
}
ctx.JSON(200, resp)
}
func Success(ctx *gin.Context, data string) {
resp := &Resp{
ErrNo: 0,
ErrMsg: "success",
Data: data,
}
ctx.JSON(200, resp)
}
type HelloResp struct {
ErrNo int `json:"err_no"`
ErrMsg string `json:"err_msg"`
Data string `json:"data"`
}
type SetNameReq struct {
Target string `json:"target"`
Name string `json:"name"`
}
type Resp struct {
ErrNo int `json:"err_no"`
ErrMsg string `json:"err_msg"`
Data interface{} `json:"data"`
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment