这是因为高版本的ubuntu没有低版本的libcrypto.so1.0 libcrypto.so.1.1文件。
可以下载指定版本并安装,即可解决找不到文件的问题。
1. 直接下载deb包
wget https://debian.mirror.ac.za/debian/pool/main/o/openssl/libssl1.1_1.1.1w-0%2Bdeb11u1_amd64.deb
sudo dpkg -i libssl1.1_1.1.1o-1_amd64.deb
2. 在官网下载源码
#从官网下载
# wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz
#腾讯云提供的镜像wget https://mirrors.cloud.tencent.com/openssl/source/openssl-1.1.1g.tar.g
#安装
tar -xvf openssl-1.1.1g.tar.gz
cd openssl-1.1.1g
./config shared --openssldir=/usr/local/openssl --prefix=/usr/local/openssl
make && make install
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
实现代码:
package main
import "fmt"
type solver interface {
play(int)
}
// towers is example of type satisfying solver interface
type towers struct {
// an empty struct
}
// play is sole method required to implement solver type
func (t *towers) play(n int) {
t.moveN(n, 1, 2, 3)
}
// recursive algorithm
func (t *towers) moveN(n, from, to, via int) {
if n > 0 {
t.moveN(n-1, from, via, to)
t.moveM(from, to)
t.moveN(n-1, via, to, from)
}
}
func (t *towers) moveM(from, to int) {
fmt.Println("Move disk from rod", from, "to rod", to)
}
func main() {
var t solver
t = new(towers) // type towers must satisfy solver interface
t.play(4)
}
输出:
Move disk from rod 1 to rod 3
Move disk from rod 1 to rod 2
Move disk from rod 3 to rod 2
Move disk from rod 1 to rod 3
Move disk from rod 2 to rod 1
Move disk from rod 2 to rod 3
Move disk from rod 1 to rod 3
Move disk from rod 1 to rod 2
Move disk from rod 3 to rod 2
Move disk from rod 3 to rod 1
Move disk from rod 2 to rod 1
Move disk from rod 3 to rod 2
Move disk from rod 1 to rod 3
Move disk from rod 1 to rod 2
Move disk from rod 3 to rod 2
这是因为无法解析proxy_pass部分的域名,
在nginx.conf配置文件中的http{}部分添加一行resolver 8.8.8.8;
执行git pull origin xxx(分支) --rebase之后先push上远程分支。
这是因为 node.js V17版本中最近发布的OpenSSL3.0, 而OpenSSL3.0对允许算法和密钥大小增加了严格的限制,可能会对生态系统造成一些影响。故此以前的项目在升级 nodejs 版本后会报错。
修改package.json,在相关构建命令之前加入SET NODE_OPTIONS=--openssl-legacy-provider,然后正常运行npm run serve即可。
"scripts": {
"serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
"build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build"
},
这是ImageMagic中有权限配置文件,需要修改PDF相关的读写权限。
# 打开policy.xml文件
sudo nano /etc/ImageMagick-6/policy.xml
# 修改PDF相关权限,由none改为read|write
<policy domain="coder" rights="none" pattern="PDF" />
# 改为
<policy domain="coder" rights="read|write" pattern="PDF" />
默认的notepad设置是文本超长时,是横向无线延申,查看内容时非常麻烦,使用工具栏的word wrap按钮即可实现根据窗口大小自动换行。
# 入口函数
```
func main() {
// cli框架:https://cli.urfave.org/
app := cli.NewApp()
// APP基础信息
app.Name = "mahjong server"
app.Author = "MaJong"
app.Version = "0.0.1"
app.Copyright = "majong team reserved"
app.Usage = "majiang server"
// 需要解析的flag
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "./configs/config.toml",
Usage: "load configuration from `FILE`",
},
cli.BoolFlag{
Name: "cpuprofile",
Usage: "enable cpu profile",
},
}
// 核心:主要功能方法
app.Action = serve
app.Run(os.Args)
}
// server方法
func serve(c *cli.Context) error {
// viper:https://github.com/spf13/viper
// 解析配置文件
viper.SetConfigType("toml")
viper.SetConfigFile(c.String("config"))
viper.ReadInConfig()
// 日志配置
log.SetFormatter(&log.TextFormatter{DisableColors: true})
if viper.GetBool("core.debug") {
log.SetLevel(log.DebugLevel)
}
// 记录cpu信息
if c.Bool("cpuprofile") {
filename := fmt.Sprintf("cpuprofile-%d.pprof", time.Now().Unix())
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
panic(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
// 开启2个goroutine
wg := sync.WaitGroup{}
wg.Add(2)
// 核心:goroutine:游戏服务
go func() { defer wg.Done(); game.Startup() }() // 开启游戏服
// 核心:goroutine:网页服务
go func() { defer wg.Done(); web.Startup() }() // 开启web服务器
// 等待gorounite结束
wg.Wait()
return nil
}
```
## 核心:goroutine:游戏服务
```
# 启动游戏服务
func Startup() {
// 随机数种子
rand.Seed(time.Now().Unix())
// 获取版本号
version = viper.GetString("update.version")
// 获取心跳配置
heartbeat := viper.GetInt("core.heartbeat")
if heartbeat < 5 {
heartbeat = 5
}
// 房卡消耗配置
csm := viper.GetString("core.consume")
SetCardConsume(csm)
forceUpdate = viper.GetBool("update.force")
logger.Infof("当前游戏服务器版本: %s, 是否强制更新: %t, 当前心跳时间间隔: %d秒", version, forceUpdate, heartbeat)
logger.Info("game service starup")
// 核心:注册游戏handler/component
comps := &component.Components{}
comps.Register(defaultManager)
comps.Register(defaultDeskManager)
comps.Register(new(ClubManager))
// 核心:加密管道
c := newCrypto()
pip := pipeline.New()
pip.Inbound().PushBack(c.inbound)
pip.Outbound().PushBack(c.outbound)
// 打印信息
addr := fmt.Sprintf(":%d", viper.GetInt("game-server.port"))
nano.Listen(addr,
nano.WithPipeline(pip),
nano.WithHeartbeatInterval(time.Duration(heartbeat)*time.Second),
nano.WithLogger(log.WithField("component", "nano")),
nano.WithSerializer(json.NewSerializer()),
nano.WithComponents(comps),
)
}
```
### 核心:component管理
```
// Components定义
// 一个component和option数组
type CompWithOptions struct {
Comp Component
Opts []Option
}
// CompWithOptions数组
type Components struct {
comps []CompWithOptions
}
// 注册方法
func (cs *Components) Register(c Component, options ...Option) {
cs.comps = append(cs.comps, CompWithOptions{c, options})
}
// 返回component列表
func (cs *Components) List() []CompWithOptions {
return cs.comps
}
```
```
// component定义
type Component interface {
Init()
AfterInit()
BeforeShutdown()
Shutdown()
}
```
#### defaultManager:游戏component
```
var defaultManager = NewManager()
// manager定义
type (
Manager struct {
component.Base // 空实现
group *nano.Group // 核心:广播channel
players map[int64]*Player // 核心:所有的玩家
chKick chan int64 // 退出队列
chReset chan int64 // 重置队列
chRecharge chan RechargeInfo // 充值信息
}
RechargeInfo struct {
Uid int64 // 用户ID
Coin int64 // 房卡数量
}
)
// 创建manager
func NewManager() *Manager {
return &Manager{
group: nano.NewGroup("_SYSTEM_MESSAGE_BROADCAST"),
players: map[int64]*Player{},
chKick: make(chan int64, kickResetBacklog),
chReset: make(chan int64, kickResetBacklog),
chRecharge: make(chan RechargeInfo, 32),
}
}
```
##### nano的Group模块
```
nano/Group.go
```
##### 麻将游戏的player逻辑
```
internal/game/player.go
```
#### defaultDeskManager:桌牌component
```
type (
DeskManager struct {
component.Base
//桌子数据
desks map[room.Number]*Desk // 所有桌子
}
)
var defaultDeskManager = NewDeskManager()
```
#### ClubManager: 俱乐部component
```
type ClubManager struct {
component.Base
}
func (c *ClubManager) ApplyClub(s *session.Session, payload *protocol.ApplyClubRequest) error {
mid := s.LastMid()
logger.Debugf("玩家申请加入俱乐部,UID=%d,俱乐部ID=%d", s.UID(), payload.ClubId)
async.Run(func() {
if err := db.ApplyClub(s.UID(), payload.ClubId); err != nil {
s.ResponseMID(mid, &protocol.ErrorResponse{
Code: -1,
Error: err.Error(),
})
} else {
s.ResponseMID(mid, &protocol.SuccessResponse)
}
})
return nil
}
```
### 核心:加密
```
// Crypto
// github.com/xxtea/xxtea-go/xxtea
type Crypto struct {
key []byte
}
func newCrypto() *Crypto {
return &Crypto{xxteaKey}
}
func (c *Crypto) inbound(s *session.Session, msg *pipeline.Message) error {
out, err := base64.StdEncoding.DecodeString(string(msg.Data))
if err != nil {
logger.Errorf("Inbound Error=%s, In=%s", err.Error(), string(msg.Data))
return err
}
out = xxtea.Decrypt(out, c.key)
if out == nil {
return fmt.Errorf("decrypt error=%s", err.Error())
}
msg.Data = out
return nil
}
func (c *Crypto) outbound(s *session.Session, msg *pipeline.Message) error {
out := xxtea.Encrypt(msg.Data, c.key)
msg.Data = []byte(base64.StdEncoding.EncodeToString(out))
return nil
}
```
### 核心:管道
```
type (
// Message is the alias of `message.Message`
Message = message.Message
Func func(s *session.Session, msg *message.Message) error
Pipeline interface {
Outbound() Channel
Inbound() Channel
}
pipeline struct {
outbound, inbound *pipelineChannel
}
Channel interface {
PushFront(h Func)
PushBack(h Func)
Process(s *session.Session, msg *message.Message) error
}
pipelineChannel struct {
mu sync.RWMutex
handlers []Func
}
)
func New() Pipeline {
return &pipeline{
outbound: &pipelineChannel{},
inbound: &pipelineChannel{},
}
}
func (p *pipeline) Outbound() Channel { return p.outbound }
func (p *pipeline) Inbound() Channel { return p.inbound }
// PushFront push a function to the front of the pipeline
func (p *pipelineChannel) PushFront(h Func) {
p.mu.Lock()
defer p.mu.Unlock()
handlers := make([]Func, len(p.handlers)+1)
handlers[0] = h
copy(handlers[1:], p.handlers)
p.handlers = handlers
}
// PushFront push a function to the end of the pipeline
func (p *pipelineChannel) PushBack(h Func) {
p.mu.Lock()
defer p.mu.Unlock()
p.handlers = append(p.handlers, h)
}
// Process process message with all pipeline functions
func (p *pipelineChannel) Process(s *session.Session, msg *message.Message) error {
p.mu.RLock()
defer p.mu.RUnlock()
if len(p.handlers) < 1 {
return nil
}
for _, h := range p.handlers {
err := h(s, msg)
if err != nil {
return err
}
}
return nil
}
```
### 核心:消息定义
```
package message
import (
"encoding/binary"
"errors"
"fmt"
"strings"
"github.com/lonng/nano/internal/log"
)
// Type represents the type of message, which could be Request/Notify/Response/Push
type Type byte
// Message types
const (
Request Type = 0x00
Notify = 0x01
Response = 0x02
Push = 0x03
)
const (
msgRouteCompressMask = 0x01
msgTypeMask = 0x07
msgRouteLengthMask = 0xFF
msgHeadLength = 0x02
)
var types = map[Type]string{
Request: "Request",
Notify: "Notify",
Response: "Response",
Push: "Push",
}
func (t Type) String() string {
return types[t]
}
var (
routes = make(map[string]uint16) // route map to code
codes = make(map[uint16]string) // code map to route
)
// Errors that could be occurred in message codec
var (
ErrWrongMessageType = errors.New("wrong message type")
ErrInvalidMessage = errors.New("invalid message")
ErrRouteInfoNotFound = errors.New("route info not found in dictionary")
)
// Message represents a unmarshaled message or a message which to be marshaled
type Message struct {
Type Type // message type
ID uint64 // unique id, zero while notify mode
Route string // route for locating service
Data []byte // payload
compressed bool // is message compressed
}
// New returns a new message instance
func New() *Message {
return &Message{}
}
// String, implementation of fmt.Stringer interface
func (m *Message) String() string {
return fmt.Sprintf("%s %s (%dbytes)", types[m.Type], m.Route, len(m.Data))
}
// Encode marshals message to binary format.
func (m *Message) Encode() ([]byte, error) {
return Encode(m)
}
func routable(t Type) bool {
return t == Request || t == Notify || t == Push
}
func invalidType(t Type) bool {
return t < Request || t > Push
}
// Encode marshals message to binary format. Different message types is corresponding to
// different message header, message types is identified by 2-4 bit of flag field. The
// relationship between message types and message header is presented as follows:
// ------------------------------------------
// | type | flag | other |
// |----------|--------|--------------------|
// | request |----000-|<message id>|<route>|
// | notify |----001-|<route> |
// | response |----010-|<message id> |
// | push |----011-|<route> |
// ------------------------------------------
// The figure above indicates that the bit does not affect the type of message.
// See ref: https://github.com/lonnng/nano/blob/master/docs/communication_protocol.md
func Encode(m *Message) ([]byte, error) {
if invalidType(m.Type) {
return nil, ErrWrongMessageType
}
buf := make([]byte, 0)
flag := byte(m.Type) << 1
code, compressed := routes[m.Route]
if compressed {
flag |= msgRouteCompressMask
}
buf = append(buf, flag)
if m.Type == Request || m.Type == Response {
n := m.ID
// variant length encode
for {
b := byte(n % 128)
n >>= 7
if n != 0 {
buf = append(buf, b+128)
} else {
buf = append(buf, b)
break
}
}
}
if routable(m.Type) {
if compressed {
buf = append(buf, byte((code>>8)&0xFF))
buf = append(buf, byte(code&0xFF))
} else {
buf = append(buf, byte(len(m.Route)))
buf = append(buf, []byte(m.Route)...)
}
}
buf = append(buf, m.Data...)
return buf, nil
}
// Decode unmarshal the bytes slice to a message
// See ref: https://github.com/lonnng/nano/blob/master/docs/communication_protocol.md
func Decode(data []byte) (*Message, error) {
if len(data) < msgHeadLength {
return nil, ErrInvalidMessage
}
m := New()
flag := data[0]
offset := 1
m.Type = Type((flag >> 1) & msgTypeMask)
if invalidType(m.Type) {
return nil, ErrWrongMessageType
}
if m.Type == Request || m.Type == Response {
id := uint64(0)
// little end byte order
// WARNING: must can be stored in 64 bits integer
// variant length encode
for i := offset; i < len(data); i++ {
b := data[i]
id += uint64(b&0x7F) << uint64(7*(i-offset))
if b < 128 {
offset = i + 1
break
}
}
m.ID = id
}
if routable(m.Type) {
if flag&msgRouteCompressMask == 1 {
m.compressed = true
code := binary.BigEndian.Uint16(data[offset:(offset + 2)])
route, ok := codes[code]
if !ok {
return nil, ErrRouteInfoNotFound
}
m.Route = route
offset += 2
} else {
m.compressed = false
rl := data[offset]
offset++
m.Route = string(data[offset:(offset + int(rl))])
offset += int(rl)
}
}
m.Data = data[offset:]
return m, nil
}
// SetDictionary set routes map which be used to compress route.
// TODO(warning): set dictionary in runtime would be a dangerous operation!!!!!!
func SetDictionary(dict map[string]uint16) {
for route, code := range dict {
r := strings.TrimSpace(route)
// duplication check
if _, ok := routes[r]; ok {
log.Println(fmt.Sprintf("duplicated route(route: %s, code: %d)", r, code))
}
if _, ok := codes[code]; ok {
log.Println(fmt.Sprintf("duplicated route(route: %s, code: %d)", r, code))
}
// update map, using last value when key duplicated
routes[r] = code
codes[code] = r
}
}
```
### Listen
```
nano.Listen(addr,
nano.WithPipeline(pip),
nano.WithHeartbeatInterval(time.Duration(heartbeat)*time.Second),
nano.WithLogger(log.WithField("component", "nano")),
nano.WithSerializer(json.NewSerializer()),
nano.WithComponents(comps),
```
```
func Listen(addr string, opts ...Option) {
if atomic.AddInt32(&running, 1) != 1 {
log.Println("Nano has running")
return
}
// application initialize
app.name = strings.TrimLeft(filepath.Base(os.Args[0]), "/")
app.startAt = time.Now()
// environment initialize
if wd, err := os.Getwd(); err != nil {
panic(err)
} else {
env.Wd, _ = filepath.Abs(wd)
}
opt := cluster.Options{
Components: &component.Components{},
}
for _, option := range opts {
option(&opt)
}
// Use listen address as client address in non-cluster mode
if !opt.IsMaster && opt.AdvertiseAddr == "" && opt.ClientAddr == "" {
log.Println("The current server running in singleton mode")
opt.ClientAddr = addr
}
// Set the retry interval to 3 secondes if doesn't set by user
if opt.RetryInterval == 0 {
opt.RetryInterval = time.Second * 3
}
node := &cluster.Node{
Options: opt,
ServiceAddr: addr,
}
err := node.Startup()
if err != nil {
log.Fatalf("Node startup failed: %v", err)
}
runtime.CurrentNode = node
if node.ClientAddr != "" {
log.Println(fmt.Sprintf("Startup *Nano gate server* %s, client address: %v, service address: %s",
app.name, node.ClientAddr, node.ServiceAddr))
} else {
log.Println(fmt.Sprintf("Startup *Nano backend server* %s, service address %s",
app.name, node.ServiceAddr))
}
go scheduler.Sched()
sg := make(chan os.Signal)
signal.Notify(sg, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM)
select {
case <-env.Die:
log.Println("The app will shutdown in a few seconds")
case s := <-sg:
log.Println("Nano server got signal", s)
}
log.Println("Nano server is stopping...")
node.Shutdown()
runtime.CurrentNode = nil
scheduler.Close()
atomic.StoreInt32(&running, 0)
}
```