feat: add readme
This commit is contained in:
		
							parent
							
								
									6fe39b6c61
								
							
						
					
					
						commit
						ef924fb073
					
				| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<project version="4">
 | 
				
			||||||
 | 
					  <component name="VcsDirectoryMappings">
 | 
				
			||||||
 | 
					    <mapping directory="$PROJECT_DIR$" vcs="Git" />
 | 
				
			||||||
 | 
					  </component>
 | 
				
			||||||
 | 
					</project>
 | 
				
			||||||
							
								
								
									
										7
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										7
									
								
								Makefile
								
								
								
								
							| 
						 | 
					@ -32,10 +32,5 @@ gen-api: # 產生 api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: mock-gen
 | 
					.PHONY: mock-gen
 | 
				
			||||||
mock-gen: # 建立 mock 資料
 | 
					mock-gen: # 建立 mock 資料
 | 
				
			||||||
	mockgen -source=./internal/module/product/domain/repository/category.go -destination=./internal/module/product/mock/repository/category.go -package=mock
 | 
						mockgen -source=./internal/module/url/repository/url.go -destination=./internal/module/url//mock/repository/url.go -package=mock
 | 
				
			||||||
	mockgen -source=./internal/module/product/domain/repository/supplementary_info.go -destination=./internal/module/product/mock/repository/supplementary_info.go -package=mock
 | 
					 | 
				
			||||||
	mockgen -source=./internal/module/product/domain/repository/product.go -destination=./internal/module/product/mock/repository/product.go -package=mock
 | 
					 | 
				
			||||||
	mockgen -source=./internal/module/product/domain/repository/tags.go -destination=./internal/module/product/mock/repository/tags.go -package=mock
 | 
					 | 
				
			||||||
	mockgen -source=./internal/module/product/domain/repository/product_item.go -destination=./internal/module/product/mock/repository/product_item.go -package=mock
 | 
					 | 
				
			||||||
	mockgen -source=./internal/module/cart/domain/repository/cart.go -destination=./internal/module/cart/mock/repository/cart.go -package=mock
 | 
					 | 
				
			||||||
	@echo "Generate mock files successfully"
 | 
						@echo "Generate mock files successfully"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					###########
 | 
				
			||||||
 | 
					# BUILDER #
 | 
				
			||||||
 | 
					###########
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FROM golang:1.22.3 as builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARG VERSION
 | 
				
			||||||
 | 
					ARG BUILT
 | 
				
			||||||
 | 
					ARG GIT_COMMIT
 | 
				
			||||||
 | 
					ARG SSH_PRV_KEY
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# private go packages
 | 
				
			||||||
 | 
					ENV GOPRIVATE=code.30cm.net
 | 
				
			||||||
 | 
					ENV FLAG="-s -w -X main.Version=${VERSION} -X main.Built=${BUILT} -X main.GitCommit=${GIT_COMMIT}"
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					COPY . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update && \
 | 
				
			||||||
 | 
					    apt-get install git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Make the root foler for our ssh
 | 
				
			||||||
 | 
					RUN mkdir -p /root/.ssh && \
 | 
				
			||||||
 | 
					    chmod 0700 /root/.ssh && \
 | 
				
			||||||
 | 
					    ssh-keyscan git.30cm.net > /root/.ssh/known_hosts && \
 | 
				
			||||||
 | 
					    echo "$SSH_PRV_KEY" > /root/.ssh/id_rsa && \
 | 
				
			||||||
 | 
					    chmod 600 /root/.ssh/id_rsa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN --mount=type=ssh go mod download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
 | 
				
			||||||
 | 
					    -ldflags "$FLAG" \
 | 
				
			||||||
 | 
					    -o url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					##########
 | 
				
			||||||
 | 
					## FINAL #
 | 
				
			||||||
 | 
					##########
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					FROM gcr.io/distroless/static-debian11
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY --from=builder /app/url /app/url
 | 
				
			||||||
 | 
					COPY --from=builder /app/etc/url.yaml /app/etc/url.yaml
 | 
				
			||||||
 | 
					EXPOSE 8888
 | 
				
			||||||
 | 
					CMD ["/app/url"]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					version: "3.9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					services:
 | 
				
			||||||
 | 
					  app:
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: . # 當前目錄為構建上下文
 | 
				
			||||||
 | 
					      dockerfile: ./build/Dockerfile
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      - VERSION=1.0.0
 | 
				
			||||||
 | 
					      - BUILT=$(date +%Y-%m-%d)
 | 
				
			||||||
 | 
					      - GIT_COMMIT=$(git rev-parse --short HEAD)
 | 
				
			||||||
 | 
					      - SSH_PRV_KEY=${SSH_PRV_KEY} # 傳遞私鑰,用於私有倉庫
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "8888:8888"
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - mongo
 | 
				
			||||||
 | 
					      - redis
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./etc:/app/etc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mongo:
 | 
				
			||||||
 | 
					    image: mongo:8.0
 | 
				
			||||||
 | 
					    container_name: mongo
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "27017:27017"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  redis:
 | 
				
			||||||
 | 
					    image: redis:7.0
 | 
				
			||||||
 | 
					    container_name: redis
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "6379:6379"
 | 
				
			||||||
| 
						 | 
					@ -7,8 +7,8 @@ Cache:
 | 
				
			||||||
    type: node
 | 
					    type: node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Mongo:
 | 
					Mongo:
 | 
				
			||||||
  Schema: 
 | 
					  Schema: mongodb
 | 
				
			||||||
  Host:
 | 
					  Host: "127.0.0.1:27017"
 | 
				
			||||||
  User:
 | 
					  User:
 | 
				
			||||||
  Password:
 | 
					  Password:
 | 
				
			||||||
  Database: digimon_url
 | 
					  Database: digimon_url
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					// Code generated by MockGen. DO NOT EDIT.
 | 
				
			||||||
 | 
					// Source: ./internal/module/url/repository/url.go
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Generated by this command:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//	mockgen -source=./internal/module/url/repository/url.go -destination=./internal/module/url//mock/repository/url.go -package=mock
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Package mock is a generated GoMock package.
 | 
				
			||||||
 | 
					package mock
 | 
				
			||||||
| 
						 | 
					@ -173,8 +173,3 @@ func (repo *URLRepository) Index20241226001UP(ctx context.Context) (*mongo.Curso
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return repo.DB.GetClient().Indexes().List(ctx)
 | 
						return repo.DB.GetClient().Indexes().List(ctx)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// ptr 是一個幫助函數,用於生成指針
 | 
					 | 
				
			||||||
func ptr[T any](v T) *T {
 | 
					 | 
				
			||||||
	return &v
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package repository
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	mgo "code.30cm.net/digimon/library-go/mongo"
 | 
						mgo "code.30cm.net/digimon/library-go/mongo"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"github.com/alicebob/miniredis/v2"
 | 
						"github.com/alicebob/miniredis/v2"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
	"github.com/zeromicro/go-zero/core/stores/cache"
 | 
						"github.com/zeromicro/go-zero/core/stores/cache"
 | 
				
			||||||
| 
						 | 
					@ -22,8 +23,7 @@ func SetupTestURLRepository(db string) (repository.URLRepository, func(), error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	conf := &mgo.Conf{
 | 
						conf := &mgo.Conf{
 | 
				
			||||||
		Schema:                           "mongodb",
 | 
							Schema:                           "mongodb",
 | 
				
			||||||
		Host:                             h,
 | 
							Host:                             fmt.Sprintf("%s:%s", h, p),
 | 
				
			||||||
		Port:                             p,
 | 
					 | 
				
			||||||
		Database:                         db,
 | 
							Database:                         db,
 | 
				
			||||||
		MaxStaleness:                     300,
 | 
							MaxStaleness:                     300,
 | 
				
			||||||
		MaxPoolSize:                      100,
 | 
							MaxPoolSize:                      100,
 | 
				
			||||||
| 
						 | 
					@ -50,9 +50,9 @@ func SetupTestURLRepository(db string) (repository.URLRepository, func(), error)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	param := URLRepositoryParam{
 | 
						param := URLRepositoryParam{
 | 
				
			||||||
		conf:      conf,
 | 
							Conf:      conf,
 | 
				
			||||||
		cacheConf: cacheConf,
 | 
							CacheConf: cacheConf,
 | 
				
			||||||
		cacheOpts: cacheOpts,
 | 
							CacheOpts: cacheOpts,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo := NewURLRepository(param)
 | 
						repo := NewURLRepository(param)
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,7 @@ func SetupTestURLRepository(db string) (repository.URLRepository, func(), error)
 | 
				
			||||||
	return repo, tearDown, nil
 | 
						return repo, tearDown, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestAccountModel_Insert(t *testing.T) {
 | 
					func TestAccountModel_InsertMany(t *testing.T) {
 | 
				
			||||||
	repo, tearDown, err := SetupTestURLRepository("testDB")
 | 
						repo, tearDown, err := SetupTestURLRepository("testDB")
 | 
				
			||||||
	defer tearDown()
 | 
						defer tearDown()
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
| 
						 | 
					@ -74,7 +74,7 @@ func TestAccountModel_Insert(t *testing.T) {
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "Valid account insert",
 | 
								name: "Valid account insert",
 | 
				
			||||||
			account: &entity.URLTable{
 | 
								account: &entity.URLTable{
 | 
				
			||||||
				ShortCode: "123",
 | 
									ShortCode: "AAAAA",
 | 
				
			||||||
				URL:       "https://www.google.com",
 | 
									URL:       "https://www.google.com",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			expectError: false,
 | 
								expectError: false,
 | 
				
			||||||
| 
						 | 
					@ -89,23 +89,10 @@ func TestAccountModel_Insert(t *testing.T) {
 | 
				
			||||||
				assert.Error(t, err)
 | 
									assert.Error(t, err)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				assert.NoError(t, err, "插入操作應該成功")
 | 
									assert.NoError(t, err, "插入操作應該成功")
 | 
				
			||||||
 | 
									one, err := repo.FindOne(context.Background(), tt.account.ShortCode)
 | 
				
			||||||
				//// 檢查是否生成了 ObjectID 和時間戳
 | 
									assert.NoError(t, err)
 | 
				
			||||||
				//assert.NotZero(t, tt.account.ID, "ID 應該被生成")
 | 
									assert.Equal(t, one.ShortCode, tt.account.ShortCode)
 | 
				
			||||||
				//assert.NotNil(t, tt.account.CreateAt, "CreateAt 應該被設置")
 | 
									assert.Equal(t, one.URL, tt.account.URL)
 | 
				
			||||||
				//assert.NotNil(t, tt.account.UpdateAt, "UpdateAt 應該被設置")
 | 
					 | 
				
			||||||
				//
 | 
					 | 
				
			||||||
				//// 檢查插入的時間是否合理
 | 
					 | 
				
			||||||
				//now := time.Now().UTC().UnixNano()
 | 
					 | 
				
			||||||
				//assert.LessOrEqual(t, *tt.account.CreateAt, now, "CreateAt 應在當前時間之前")
 | 
					 | 
				
			||||||
				//assert.LessOrEqual(t, *tt.account.UpdateAt, now, "UpdateAt 應在當前時間之前")
 | 
					 | 
				
			||||||
				//
 | 
					 | 
				
			||||||
				//// 確認插入的資料
 | 
					 | 
				
			||||||
				//insertedAccount, err := repo.FindOne(context.Background(), tt.account.ID.Hex())
 | 
					 | 
				
			||||||
				//assert.NoError(t, err, "應該可以找到插入的帳號資料")
 | 
					 | 
				
			||||||
				//assert.Equal(t, tt.account.LoginID, insertedAccount.LoginID, "LoginID 應相同")
 | 
					 | 
				
			||||||
				//assert.Equal(t, tt.account.Token, insertedAccount.Token, "Token 應相同")
 | 
					 | 
				
			||||||
				//assert.Equal(t, tt.account.Platform, insertedAccount.Platform, "Platform 應相同")
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,6 +48,7 @@ func (alloc *IDAllocator) Release(id string) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 將對應位設置為 0
 | 
						// 將對應位設置為 0
 | 
				
			||||||
	alloc.bitmap.SetBit(alloc.bitmap, index, 0)
 | 
						alloc.bitmap.SetBit(alloc.bitmap, index, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					package usecase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestIDAllocator(t *testing.T) {
 | 
				
			||||||
 | 
						allocator := NewIDAllocator()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 測試分配 ID
 | 
				
			||||||
 | 
						t.Run("Allocate IDs", func(t *testing.T) {
 | 
				
			||||||
 | 
							id1, err := allocator.Allocate()
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.NotEmpty(t, id1, "Allocated ID should not be empty")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							id2, err := allocator.Allocate()
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.NotEmpty(t, id2, "Allocated ID should not be empty")
 | 
				
			||||||
 | 
							assert.NotEqual(t, id1, id2, "Allocated IDs should be unique")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 測試釋放 ID
 | 
				
			||||||
 | 
						t.Run("Release ID", func(t *testing.T) {
 | 
				
			||||||
 | 
							id, err := allocator.Allocate()
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = allocator.Release(id)
 | 
				
			||||||
 | 
							assert.NoError(t, err, "Releasing an allocated ID should succeed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 重新分配應該得到相同的 ID
 | 
				
			||||||
 | 
							newID, err := allocator.Allocate()
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.Equal(t, id, newID, "Reallocated ID should be the same as the released ID")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 測試釋放無效的 ID
 | 
				
			||||||
 | 
						t.Run("Release invalid ID", func(t *testing.T) {
 | 
				
			||||||
 | 
							err := allocator.Release("錯誤")
 | 
				
			||||||
 | 
							assert.Error(t, err, "Releasing an invalid ID should return an error")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 測試超過位圖容量的情況
 | 
				
			||||||
 | 
						t.Run("Exceed bitmap capacity", func(t *testing.T) {
 | 
				
			||||||
 | 
							// 分配 64 個 ID
 | 
				
			||||||
 | 
							for i := 0; i < 61; i++ {
 | 
				
			||||||
 | 
								_, err := allocator.Allocate()
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 第 65 次分配應該返回錯誤
 | 
				
			||||||
 | 
							_, err := allocator.Allocate()
 | 
				
			||||||
 | 
							assert.Error(t, err, "Allocating more than 64 IDs should fail")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestEncodeDecodeIndex(t *testing.T) {
 | 
				
			||||||
 | 
						t.Run("Encode and Decode", func(t *testing.T) {
 | 
				
			||||||
 | 
							for i := 0; i < 64; i++ {
 | 
				
			||||||
 | 
								encoded := encodeIndex(i)
 | 
				
			||||||
 | 
								decoded, err := decodeIndex(encoded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
								assert.Equal(t, i, decoded, "Decoded index should match the original")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("Decode invalid string", func(t *testing.T) {
 | 
				
			||||||
 | 
							_, err := decodeIndex("不可以")
 | 
				
			||||||
 | 
							assert.Error(t, err, "Decoding an invalid ID should return an error")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
package svc
 | 
					package svc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"github.com/zeromicro/go-zero/core/stores/cache"
 | 
						"github.com/zeromicro/go-zero/core/stores/cache"
 | 
				
			||||||
	"github.com/zeromicro/go-zero/core/stores/mon"
 | 
						"github.com/zeromicro/go-zero/core/stores/mon"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
| 
						 | 
					@ -30,6 +31,10 @@ func NewServiceContext(c config.Config) *ServiceContext {
 | 
				
			||||||
			cache.WithNotFoundExpiry(DefaultFindDataNotFoundTimeout),
 | 
								cache.WithNotFoundExpiry(DefaultFindDataNotFoundTimeout),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
						_, err := ur.Index20241226001UP(context.Background())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	urlUseCase := uc.MustURLUseCase(uc.URLUseCaseParam{
 | 
						urlUseCase := uc.MustURLUseCase(uc.URLUseCaseParam{
 | 
				
			||||||
		URLRepo: ur,
 | 
							URLRepo: ur,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,111 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 本地端啟動(測試用)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[//]: # (請先設定資料庫)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					go mod tidy
 | 
				
			||||||
 | 
					# 進入 etc/gateway.yml 設定各依賴的 config
 | 
				
			||||||
 | 
					go run url.go
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 本地啟動需要依賴套件或工具
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					以下已 macOS 為例,若已安裝或有其他可用服務則可跳過
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- go-zero goctl (https://go-zero.dev/docs/tasks/installation/goctl)
 | 
				
			||||||
 | 
					  ```bash
 | 
				
			||||||
 | 
					  $ go install github.com/zeromicro/go-zero/tools/goctl@latest
 | 
				
			||||||
 | 
					  ```
 | 
				
			||||||
 | 
					- protobuf (https://protobuf.dev/)
 | 
				
			||||||
 | 
					  ```bash
 | 
				
			||||||
 | 
					  brew install protobuf
 | 
				
			||||||
 | 
					  ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Docker 啟動 (Production)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					make build-docker
 | 
				
			||||||
 | 
					make run-docker
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 目錄結構
 | 
				
			||||||
 | 
					```aiignore
 | 
				
			||||||
 | 
					├── Makefile                        # 用於構建和運行專案的自動化腳本
 | 
				
			||||||
 | 
					├── etc                             
 | 
				
			||||||
 | 
					│   └── url.yaml                   # 服務配置檔,例如端口號、資料庫連線等
 | 
				
			||||||
 | 
					├── generate                        # 產生服務的目錄
 | 
				
			||||||
 | 
					│   └── api                        
 | 
				
			||||||
 | 
					│       └── url_generate.api       # API 定義檔,描述服務介面和資料結構
 | 
				
			||||||
 | 
					├── go.mod                         
 | 
				
			||||||
 | 
					├── go.sum                         
 | 
				
			||||||
 | 
					├── internal                        # 內部模組目錄,不對外暴露
 | 
				
			||||||
 | 
					│   ├── config                     
 | 
				
			||||||
 | 
					│   │   └── config.go              # 配置管理邏輯,負責加載和解析配置檔
 | 
				
			||||||
 | 
					│   ├── handler                    
 | 
				
			||||||
 | 
					│   │   ├── routes.go              # 路由註冊檔案,將請求分發到對應的處理器
 | 
				
			||||||
 | 
					│   │   └── url                    
 | 
				
			||||||
 | 
					│   │       └──                    # URL 相關請求處理器目錄,具體實現短網址的創建、查詢等功能
 | 
				
			||||||
 | 
					│   ├── logic                      
 | 
				
			||||||
 | 
					│   │   └── url                    
 | 
				
			||||||
 | 
					│   │       └──                    # 核心業務邏輯層,實現短網址生成、存儲和讀取
 | 
				
			||||||
 | 
					│   ├── module                     
 | 
				
			||||||
 | 
					│   │   └── url                    
 | 
				
			||||||
 | 
					│   │       └──                    # URL 子模組的實現,封裝功能細節
 | 
				
			||||||
 | 
					│   ├── svc                        
 | 
				
			||||||
 | 
					│   │   └── service_context.go     # 服務上下文,管理全域依賴(如資料庫連線等)
 | 
				
			||||||
 | 
					│   └── types                      
 | 
				
			||||||
 | 
					│       └── types.go               # 通用型別定義,例如請求和回應的結構體
 | 
				
			||||||
 | 
					├── readme.md                       # 專案說明檔,描述專案目標、使用方法等
 | 
				
			||||||
 | 
					├── url.go                          # 服務的入口檔案,負責啟動服務
 | 
				
			||||||
 | 
					└── url.json                        # 服務元數據或範例配置檔,用於輔助開發或測試
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 設計思路
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					IDAllocator 是一個基於 Bitmap的簡單 ID 分配器,用於管理有限數量的 ID。通過位圖來標記 ID 的使用狀態,每個位對應一個唯一的 ID,從而高效地分配和釋放資源。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 核心設計理念
 | 
				
			||||||
 | 
					  1. 使用 math/big 提供的 big.Int 作為位圖,記錄每個 ID 是否已被分配。
 | 
				
			||||||
 | 
					  2. 同步安全:通過 sync.Mutex 確保分配和釋放操作的線程安全性。
 | 
				
			||||||
 | 
					  3. 編碼與解碼:將位圖中的索引轉換為 URL 友好的短碼(使用 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ 作為字符集),以便對外提供友好的短字符串表示。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 功能說明
 | 
				
			||||||
 | 
					#### 分配 ID (Allocate)
 | 
				
			||||||
 | 
					  * 從位圖中找到第一個未使用的位置,分配對應的 ID。
 | 
				
			||||||
 | 
					  * 將該位設為 1 表示已分配,並返回經過編碼的短字符串。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 釋放 ID (Release)
 | 
				
			||||||
 | 
					  * 將傳入的 ID 解碼為位圖索引。
 | 
				
			||||||
 | 
					  * 將對應位設為 0,表示該 ID 已釋放,可重複使用。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### ID 編碼 (encodeIndex)
 | 
				
			||||||
 | 
					  * 將位圖索引轉換為固定長度(5 字符)的短碼。 -> **故目前可用會是64 的五次方個(考慮單機架構),已夠用,如果需要分散式可以再討論**
 | 
				
			||||||
 | 
					  * 確保生成的 ID 短小且唯一。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### ID 解碼 (decodeIndex)
 | 
				
			||||||
 | 
					  * 將短碼還原為位圖索引,確保釋放或其他操作可以正確定位。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 限制與考量
 | 
				
			||||||
 | 
					  1. 前可用會是 64 的五次方個(考慮單機架構),已夠用,如果需要分散式可以再討論
 | 
				
			||||||
 | 
					  2. 目前設計並沒有考慮壞掉重啟,只有讓他不斷插入,在資料庫設定為一所以,插入不了就會在產新的,的消極處理方案
 | 
				
			||||||
 | 
					      如果需求需要重啟復原,可以在討論如何做
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 分布式設計考量
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 如果需要支持分布式環境,需引入全局一致性和協作管理,可能的改進方向包括:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.	集中式位圖管理:
 | 
				
			||||||
 | 
					   * 使用集中式存儲(例如 Redis 或 Zookeeper)存儲位圖數據。
 | 
				
			||||||
 | 
					   * 各實例在分配和釋放時,通過原子操作更新集中位圖。
 | 
				
			||||||
 | 
					2.	分段分配:
 | 
				
			||||||
 | 
					   * 將 ID 空間劃分為多段,分配給不同的節點,每個節點管理自己的位圖。
 | 
				
			||||||
 | 
					   * 節點之間通過協議(如 Raft)協作管理段的分配。
 | 
				
			||||||
 | 
					3.	雪花算法(Snowflake Algorithm):
 | 
				
			||||||
 | 
					   * 基於時間戳、機器 ID 和序列號生成全局唯一的 ID。
 | 
				
			||||||
 | 
					   * 適用於大規模分布式系統。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					時間尚短,測試先只做最基本的short_code_utils
 | 
				
			||||||
		Loading…
	
		Reference in New Issue