1.前置准备

  • 安装内置的kitex命令行工具
    1
    2

    go install code.byted.org/kite/kitex/tool/cmd/kitex@latest
  • 安装带idl管理功能的kitex工具
    1
    wget --header="X-Tos-Access: internal" "http://kitex.tos-cn-north.byted.org/kx/kx-macos" -O /usr/local/bin/kx && chmod a+x /usr/local/bin/kx

目录

Go

C++

Go

1.转移函数StructTo()

  • 用于结构体、map、json格式的字符串之间的数据转移
  • 依据是tag的json标签
  • 注意,使用时要StructTo(&xx1, &xx2)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    tp := reflect.TypeOf(old)
    var bt []byte
    var err error
    if tp.Name() == "string" {
    bt = []byte(old.(string))
    } else {
    bt, err = json.Marshal(old)
    if err != nil {
    return err
    }
    }
    err = json.Unmarshal(bt, new)
    if err != nil {
    return err
    }
    return nil

2.反转数组

1
2
3
4
5
6
7
func ReverseSlice(s interface{}) {
size := reflect.ValueOf(s).Len()
swap := reflect.Swapper(s)
for i, j := 0, size-1; i < j; i, j = i+1, j-1 {
swap(i, j)
}
}

3.正则判断字符串格式

  • 判断手机号
    1
    2
    3
    4
    5
    6
    func VerifyMobileFormat(mobileNum string) bool {
    regular := "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"

    reg := regexp.MustCompile(regular)
    return reg.MatchString(mobileNum)
    }
  • 判断身份证号
    1
    2
    3
    4
    5
    6
    7
    8
    func VerifyIdFormat(id string) bool {
    if len(id) < 18 {
    return false
    }
    pattern := `^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$`
    reg := regexp.MustCompile(pattern)
    return reg.MatchString(id)
    }
  • 判断邮箱
    1
    2
    3
    4
    5
    6
    func VerifyEmailFormat(email string) bool {
    pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$`

    reg := regexp.MustCompile(pattern)
    return reg.MatchString(email)
    }

4.md5加密明文

  • 一般使用这样的形式保存用户密码Md5Make(password + salt + ApiSecret)
    • salt是用户表里每个用户独有随机生成的字段
    • ApiSecret是config配置文件里配置的字符串
    • 好处是攻击者获得数据库或获得config文件之一都不能破解密码,只有二者都具备才能解密
      1
      2
      3
      4
      5
      func Md5Make(s string) string {
      md5 := md52.New()
      md5.Write([]byte(s))
      return hex.EncodeToString(md5.Sum(nil))
      }

5.项目根据配置文件初始化

  • 初始化配置文件config.json,以有user和admin两个端的项目为例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    {
    "userWeb" : {
    "api_port" : "8085",
    "api_secret": "RaFmEoUgGINWwHecXGEhfKogPfRyzgfL",
    "save_dir" : "/api/upload/user_web/"
    },
    "adminWeb" : {
    "api_port" : "8086",
    "api_secret" : "gLhUdClFoIfppzAUzYZigbaEEdaOwYQc",
    "save_dir" : "/api/upload/admin_web/"
    },
    "mysql" : {
    "driver": "mysql",
    "user": "root",
    "pass_word": "123456",
    "host": "121.5.184.118",
    "port": "3306",
    "db_name": "xy",
    "charset": "utf8mb4",
    "show_sql": true, //用于给engine.ShowSQL(mysql.ShowSql)调用,表示是否往控制台打印sql语句
    "parseTime": "true",
    "loc": "Asia/Shanghai"
    }
    }
  • 初始化方法ConfigInit()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    type Config struct {
    Mysql Mysql
    UserWeb UserWeb
    AdminWeb AdminWeb
    }
    type Mysql struct {
    Driver string `json:"driver"`
    User string `json:"user"`
    PassWord string `json:"pass_word"`
    Host string `json:"host"`
    Port string `json:"port"`
    DbName string `json:"db_name"`
    Charset string `json:"charset"`
    ShowSql bool `json:"show_sql"`
    ParseTime string `json:"parse_time"`
    Loc string `json:"loc"`
    }
    type UserWeb struct {
    ApiPort string `json:"api_port"`
    ApiSecret string `json:"api_secret"`
    SaveDir string `json:"save_dir"`
    }
    type AdminWeb struct {
    ApiPort string `json:"api_port"`
    ApiSecret string `json:"api_secret"`
    SaveDir string `json:"save_dir"`
    }

    var Conf *Config

    //path存的是config.json的路径,一般用os.GetWd()+"xx"获取
    func ConfigInit(path string) *Config {

    config := new(Config)

    file, err := os.Open(path)
    if err != nil {
    fmt.Println(err)
    panic("打开配置文件错误" + path)
    }

    confByte, err := ioutil.ReadAll(file)
    if err != nil {
    panic("读取配置文件错误")
    }

    err = json.Unmarshal(confByte, config)
    if err != nil {
    fmt.Println(err)
    fmt.Println("*************")
    fmt.Println(path)
    panic("解析配置文件错误")
    }

    Conf = config
    return Conf
    }

6.项目的main()函数

1
2
3
4
5
6
7
8
9
10
11
func main() {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
config.ConfigInit(dir + "/internal/configs/config.json") //根据配置文件初始化

router := user_web_api.RouterInit() //采用user_web_api文件夹里面的router.go

_ = router.Run(":" + config.Conf.UserWeb.ApiPort) //采用配置文件里的端口号
}

7.路由文件router.go的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func RouterInit() *gin.Engine {
//gin常规写法,过两个默认中间件
router := gin.Default()
router.Use(gin.Logger())
router.Use(gin.Recovery())

router.Use(auth.CORS()) //自己写的解决跨域问题的
mysqlStore, err := mysql.GetMysqlFactory() //使用工厂生成mysqlStore对象
store.SetClient(mysqlStore)
pkg.SetStore(mysqlStore)

app := router.Group("/xy_web_user") //设置总路由
userRouter := app.Group("/user") //设置子路由分组
{
userController := user.NewUserController(mysqlStore)
userRouter.POST("/login", userController.UserLogin)
userRouter.Use(auth.MiddleWare()) //在此之下的路由都会过中间件
userRouter.POST("/update_password", userController.UpdatePassword)
}
return router
}

8.jwt生成和解密token

  • 使用传入的user_ id、branch_id,再加上配置文件里的secret字段生成token
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    func GetToken(userId, branchId int,secret string) (string,error) {
    appSecret := secret
    jwtKey = []byte(appSecret)
    claim := &MyClaims{
    UserId: userId,
    BranchId: branchId,
    StandardClaims:jwt.StandardClaims{
    IssuedAt: time.Now().Unix(),
    Subject: "userToken",
    },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
    tokenString, err := token.SignedString(jwtKey)
    if err != nil {
    return "", err
    }

    return tokenString, nil
    }
  • 解析token
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    func ParseToken(tokenString string)(*MyClaims, error)  {
    claim := &MyClaims{}
    jwtKey = []byte(config.Conf.AdminWeb.ApiSecret)
    token,err := jwt.ParseWithClaims(tokenString,claim, func(token *jwt.Token) (interface{}, error) {
    return jwtKey,nil
    })
    if err == nil && token != nil {
    if token.Valid {
    return claim,nil
    } else {
    return nil,errors.New("token解析失败")
    }
    }
    return nil,err
    }
  • 使用
    • 生成token
      1
      token, err := jwt.GetToken(admin.Id, admin.BranchId, config.Conf.AdminWeb.ApiSecret)
    • 中间件内解析token,并将其绑定到ctx上
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      //基础写法
      myClaims, err := jwt.ParseToken(token)
      ctx.Set("user_id", myClaims.UserId)
      ctx.Set("branch_id", myClaims.BranchId)

      //另一种保证安全性的写法,即保证token解析出来的token要在数据库里存在,如果上一步由于某种原因生成了错误的token,这里就会判断不存在并返回解析失败
      myClaims, err := jwt.ParseToken(token)
      user := &models.XyAdministrator{
      Id: myClaims.UserId,
      BranchId: myClaims.BranchId,
      }
      user, flag, err := store.Client().Admin().Get(ctx, user)
      if !flag {
      code.BuildReturn(ctx, 0, "解析失败", "")
      ctx.Abort()
      return
      }
      ctx.Set("user_id", user.Id)
      ctx.Set("branch_id", user.BranchId)

9.上传文件接口的实现

  • 接受前端传的文件,得到一个*multipart.FileHeader类型变量
    1
    2
    3
    type param struct {
    File *multipart.FileHeader `form:"file" json:"file" binding:"required"`
    }
  • 用一个util包里的函数处理*multipart.FileHeader
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    func Upload(host, dir string, file *multipart.FileHeader) (string, error) {
    //获取当前运行路径
    now, err := os.Getwd()

    //创建文件夹
    dir = now + dir
    err = os.MkdirAll(dir, os.ModePerm)

    //创建空白文件
    fileName := dir + file.Filename
    f, err := os.Create(fileName)

    //打开*multipart.FileHeader文件
    defer f.Close()
    open, err := file.Open()

    //将打开的文件中的数据复制到刚刚创建的空白文件中
    _, err = io.Copy(f, open)

    url := host + fileName
    return url, nil
    }
  • 调用方式
    1
    2
    3
    host := ctx.Request.Host //服务器地址
    dir := config.Conf.UserWeb.SaveDir //文件存储路径
    url, err := util.Upload(host, dir, request.File)

10.支付的实现

  • service层
    • 新建一个rpc对象,并用以下形式发起一个rpc请求
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      req := &protos.PayRequest{
      Corp: int32(corpId), //config.json文件里写好的值
      Amount: amount,
      Order: order.OrderNo,
      Open: user.OpenId,
      Notify: "https://sbd.sc-edu.com/qyk_app/qyk_app_api/order/callback", //支付完成后微信用Post访问的回调路由
      //Notify: "www.baidu.com",
      Body: "华澳教育课程购买",
      }
      pay, err := rpcS.Pay.GetPay(ctx, req)
  • 回调路由的controller层
    • 处理回调内容,并回调rpc
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      func (c *ControllerOrder) OrderCallBack(ctx *gin.Context) {
      // 回复微信的数据
      rsp := new(wechat.NotifyResponse)
      //获取参数
      notifyReq, err := wechat.ParseNotifyToBodyMap(ctx.Request)
      fmt.Println(notifyReq)
      payTime := notifyReq["time_end"].(string)
      if err != nil {
      rsp.ReturnCode = gopay.FAIL
      ctx.String(http.StatusOK, "%s", rsp.ToXmlString())
      return
      }
      //校验签名
      ok, err := wechat.VerifySign("fia6iaFI2iUhvzsHk4rfV9w4PRZLfZVk", wechat.SignType_MD5, notifyReq)
      if !ok {
      rsp.ReturnCode = gopay.FAIL
      ctx.String(http.StatusOK, "%s", rsp.ToXmlString())
      return
      }
      order, _ := c.store.Order().GetByOrderNo(ctx, notifyReq["out_trade_no"].(string))
      if notifyReq["result_code"].(string) == "SUCCESS" && notifyReq["return_code"].(string) == "SUCCESS" {
      //rpc回调
      rpcS, _ := rpc.NewRpcService()
      req := &protos.NotifyRequest{
      Order: order.OrderNo,
      PayTime: times,
      }
      _, err = rpcS.Pay.PayNotify(ctx, req)
      if err != nil {
      fmt.Println(err.Error())
      }
      rsp.ReturnCode = gopay.SUCCESS

      ctx.String(http.StatusOK, "%s", rsp.ToXmlString())
      return
      }
      rsp.ReturnCode = gopay.FAIL
      ctx.String(http.StatusOK, "%s", rsp.ToXmlString())
      return
      }
    • 处理数据库相关字段的更新

11.处理过期15分钟的未支付订单脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"430api/internal/qyk_back_api/store/mysql"
"fmt"
"time"
)

func main() {
fmt.Println("SyncWxCourseOrder" + time.Now().Format("2006-01-02 15:04:05"))
d, _ := time.ParseDuration("-15m")
pastTime := time.Now().Add(d).Format("2006-01-02 15:04:05")

mysqlStore, _ := mysql.GetMysqlFactory()
orderList, err := mysqlStore.Order().GetListForPastTime(pastTime)
if err != nil {
fmt.Println(err)
return
}
for _, qykOrder := range orderList {
_ = mysqlStore.OrderDetail().UpdateForPastTime(qykOrder.Id)
}
fmt.Println("over")
}

C++

1.字符串分割

  • 以char分割版本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    vector<string> split(string& str, char target){
    vector<string> res;

    string temple = "";
    istringstream templeStream(str);
    while (getline(templeStream, temple, target)){
    res.push_back(temple);
    }

    return res;
    }
  • 以string分割版本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    vector<string> splitWithStl(const string &str,const string &pattern){
    vector<string> resVec;

    if ("" == str)
    {
    return resVec;
    }
    //方便截取最后一段数据
    string strs = str + pattern;

    size_t pos = strs.find(pattern);
    size_t size = strs.size();

    while (pos != string::npos)
    {
    string x = strs.substr(0,pos);
    resVec.push_back(x);
    strs = strs.substr(pos+1,size);
    pos = strs.find(pattern);
    }

    return resVec;
    }

目录

微信绑定登录

抖音小程序开发

顶象无感验证

企业微信代开发

微信绑定登录

1.实现微信绑定有两种方式

  • 微信服务号(需用户关注服务号)
  • 微信开放平台- OAuth2.0(以下采用这种方式)

2.微信开放平台OAuth2.0授权登录材料

  • 开发者账号(通过资质认证)
  • 账号下有一个已审核通过的网站应用
  • 申请微信登录功能且通过审核

3.用户使用微信登录流程:

  • 请求微信OAuth2.0授权登录:
    打开以下连接(将微信登录按钮设置成跳转到以下网址,然后后端返回微信生成的二维码)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

    # 参数说明:
    # appid:应用唯一标识(前面认证网页应用中获得)
    # redirect_uri:重定向地址,需要进行UrlEncode(前面认证网页应用中获得)
    # response_type:填code
    # scope:应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
    # 返回:
    # 二维码
  • 用户扫码授权后跳转到重定向地址并附参数code
  • 重定向的地址如下,获得code后去申请获得openid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

    # 参数说明:
    # appid:应用唯一标识,在微信开放平台提交应用审核通过后获得
    # secret:应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
    # code:填写第一步获取的code参数
    # grant_type:填authorization_code
    # 返回:
    # {
    # "access_token":"ACCESS_TOKEN",
    # "expires_in":7200,
    # "refresh_token":"REFRESH_TOKEN",
    # "openid":"OPENID",
    # "scope":"SCOPE",
    # "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
    # }
  • 使用获得的open_id在数据库中找用户资料
    • 若找到则以该用户身份生成token登录
    • 若找不到则提示未绑定

4.整体实现有两种方案:

  • 方案一:
    • 前端:显示二维码(这里可以内嵌在网页里)
    • 后端:用重定向跳转的路由接受code,用code去申请获得openid,然后与数据库中对照
  • 方案二:
    • 后端负责所有工作:显示二维码(这里只能显示一个页面来单独展示二维码),用重定向跳转的路由接受code,用code去申请获得openid,然后与数据库中对照

抖音小程序开发

1.小程序项目中单个页面会依赖不同类型的文件

  • .json 后缀的 JSON 配置文件
  • .ttml 后缀的 TTML 模板文件
  • .ttss 后缀的 TTSS 样式文件
  • .js 后缀的 JS 脚本文件

2.小程序的目录结构

1
2
3
4
5
6
7
8
9
10
11
.
├── app.js //小程序逻辑
├── app.json //小程序公共配置
├── app.ttss //小程序公共样式表
├── project.config.json //项目配置
└── pages
└── pageA
├── pageA.ttss
├── pageA.js
├── pageA.json
└── pageA.ttml

3.一个简体流程就是

  • 根目录
    • app.js和app.ttss都可以空着不写
    • app.json里改一下navigationBarTitleText用来显示标题栏名称
    • project.config.json里面填一下appid和projectname就行
  • 在pages文件夹里写
    • index.ttml文件里写网页结构,语法和html相似,但关键字不一样
    • index.js文件,用来写函数
    • index.ttss文件用来写index网页的样式

4.个位计算器的demo

  • 实现功能:两个个位数字的+-*/运算,有两个按钮计算、随机,点击随机会自动生成不同数字和运算符,点击计算会根据当前运算式显示结果
  • index.ttml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <view class="intro">Welcome to Sc-test</view>
    <view class="content">
    <!--显示块-->
    <view class="content_up_1">
    <picker class="calculation" value="{{index_1}}" range="{{array}}" data-value="1" bindchange="bindPickerChange" bindcancel="bindPickerCancel">
    <view class="picker">
    {{array[index_1]}}
    </view>
    </picker>
    <picker class="calculation" value="{{cal_num}}" range="{{cal_array}}" data-value="1" bindchange="bindPickerChange" bindcancel="bindPickerCancel">
    <view class="picker">
    {{cal_array[cal_num]}}
    </view>
    </picker>
    <picker class="calculation" value="{{index_2}}" range="{{array}}" data-value="1" bindchange="bindPickerChange" bindcancel="bindPickerCancel">
    <view class="picker">
    {{array[index_2]}}
    </view>
    </picker>
    </view>
    <!--
    <view>
    <button type="primary" bindtap="func1">页面主操作 Normal</button>
    <button type="primary" loading="true">页面主操作 Loading</button>
    <button type="primary" disabled="true">页面主操作 Disabled</button>
    </view>
    -->
    <view class="content_up_1">
    <view class="calculation_submit" bindtap="bindSubmit">计算</view>
    <view class="calculation_random" bindtap="bindRandom">随机</view>
    <!-- <view class="calculation_clear" bindtap="bindClear">清空</view> -->
    </view>

    <view class="content_down">
    <view class="text">{{result}}</view>
    </view>
  • index.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    const app = getApp()
    var that

    Page({
    data: {
    array: [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    ],
    cal_array: [
    '+', '-', '*', '/',
    ],
    index_1: 2,
    index_2: 0,
    cal_num: 0,
    result: '',
    },
    onLoad: function () {
    console.log('Welcome to Mini Code')
    },

    bindSubmit: function(e){
    var temple
    if(this.data.cal_array[this.data.cal_num] == '+') temple = this.data.array[this.data.index_1] + this.data.array[this.data.index_2]
    if(this.data.cal_array[this.data.cal_num] == '-') temple = this.data.array[this.data.index_1] - this.data.array[this.data.index_2]
    if(this.data.cal_array[this.data.cal_num] == '*') temple = this.data.array[this.data.index_1] * this.data.array[this.data.index_2]
    if(this.data.cal_array[this.data.cal_num] == '/') temple = this.data.array[this.data.index_1] / this.data.array[this.data.index_2]
    this.setData({
    result: temple,
    })
    },

    bindRandom: function (e) {
    this.setData({
    index_1: Math.floor(Math.random() * 10),//下标,0到9的随机整数
    index_2: Math.floor(Math.random() * 10),//下标,0到9的随机整数
    cal_num: Math.floor(Math.random() * 4),//下标,0到2的随机整数
    result: '',
    })
    },

    })
  • index.ttss
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    .intro {
    margin: 30px;
    text-align: center;
    }

    .my-button {
    border-radius: 20px;
    }
    .my-button:after {
    border-color: #f00;
    border-radius: 40px; /* 需要设置为按钮圆角的两倍 */
    }

    .my-button-2 {
    border: 1px solid;
    }
    .my-button-2:after {
    display: none;
    }

    .content {
    display: flex;
    flex-direction: column; /*orientation 排版 column:垂直 row:水平*/
    }

    .content_up{
    width: max-content;
    height: 300rpx;
    }

    .content_up_1{
    margin-top: 20rpx;
    width: max-content;
    display: flex;
    flex-direction: row; /*orientation 排版 column:垂直 row:水平*/
    align-items: center;
    justify-content: center;
    text-align: center;
    }

    .picker{
    height: 80rpx;
    color:#585858;
    text-align: center;
    justify-content: center;
    align-items: center;
    display: flex;
    }

    .calculation_random{
    width: 155rpx;
    height: 80rpx;
    border-radius:8rpx;
    margin-left: 22rpx;
    background: rgba(83,217,105,1);
    color:#ffffff;
    text-align: center;
    justify-content: center;
    align-items: center;
    display: flex;
    }

    .calculation_submit{
    width: 155rpx;
    height: 80rpx;
    border-radius:8rpx;
    margin-left: 22rpx;
    background: rgb(3, 78, 16);
    color:#ffffff;
    text-align: center;
    justify-content: center;
    align-items: center;
    display: flex;
    }

    .calculation{
    width: 158rpx;
    height: 80rpx;
    border-radius:8rpx;
    margin-left: 22rpx;
    border:1rpx solid rgba(209,209,209,1);
    background: #F9F9F9;
    }

    .text{
    height: 80rpx;
    color:#585858;
    text-align: center;
    justify-content: flex-start;
    align-items: center ;
    margin-left: 20rpx;
    display: flex;
    }

5.抖音小程序与后端交互

  • 前端js部分(写在某个函数里面的)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const task = tt.request({
    url: '127.0.0.1:8080/index/bar', // 目标服务器url
    dataType: 'string',
    success: (res) => {
    this.setData({
    result: res.data,
    })
    },
    fail: (res) => {
    console.log('get失败')
    this.setData({
    err_inf: res.errMsg,
    })
    },
    });
  • 后端部分
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func main() {
    router := gin.Default()
    router.GET("/index/bar", response)
    router.Run()
    }
    func response(c *gin.Context) {
    str := "123456789"
    c.String(200, str)
    fmt.Println(str)
    }

顶象无感验证

1.使用无感验证之前需要在顶象控制台新建一个实例

  • 输入:自己页面运行的域名
  • 输出:生成独有的appid、appsecret和接入域名(需要在后端和前端手动配置)

2.两种模式,在顶象自己的控制台上切换

  • 用户通过人机识别后,开启无感验证,利用地理位置等信息记忆该用户,后续不需要再进行人机识别,用户每次滑动人机验证成功后就会得到一个token
  • 不使用无感验证,每次使用都需要进行人机识别

3.前端三种表现形式

  • 嵌入式,直接在网页上展示完全验证界面
    1
    2
    3
    4
    5
    6
    7
    8
    var demo_1 = _dx.Captcha(document.getElementById('demo-embed'), {
    appId: appId,
    style: 'embed',
    width: 300,
    success: function(token) {
    window.console && console.log('success, token:', token)
    }
    })
  • 向上浮现的内联式,一个滚动条,用户拉取时才在其上方浮现验证界面
    1
    2
    3
    4
    5
    var demo_2 = _dx.Captcha(document.getElementById('demo-inline-up'), {
    appId: appId,
    style: 'inline',
    language: 'en' // 表示滚动条内的语言为英语
    })
  • 向下浮现的内联式
    1
    2
    3
    4
    5
    var demo_3 = _dx.Captcha(document.getElementById('demo-inline-down'), {
    appId: appId,
    style: 'inline',
    inlineFloatPosition: 'down'
    })
  • 弹出式,一个按钮,点击后弹出验证界面
    1
    2
    3
    4
    5
    6
    7
    8
    var demo_4 = _dx.Captcha(document.getElementById('demo-popup'), {
    appId: appId,
    style: 'popup'
    })

    document.getElementById('btn-popup').onclick = function() {
    demo_4.show()
    }

4.Go写的后端(gin框架)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

import (
"Captcha_Project/captcha-client"
"fmt"
"github.com/gin-gonic/gin"
)

func main(){
router := gin.Default()
router.LoadHTMLGlob("tmp/*")
router.GET("/index", show_index)
router.GET("/index/done", show_done)
router.GET("/index/bar", response)
router.Run()
}

func show_index(c *gin.Context){
fmt.Println("show_index")
c.HTML(200, "index.html", gin.H{"title": "测试"})
}

func show_done(c *gin.Context){
fmt.Println("show_done")
c.HTML(200, "done.html", gin.H{"title": "done"})
}

func response(c *gin.Context){
fmt.Println("response")
token := c.Query("token")
fmt.Println(token)
Run_verify(token)
}

func Run_verify(token string) {
/*
参数token在前端完成验证后可以获取到,随业务请求发送到后台,有效期为两分钟
参数ip(string)可选,提交业务参数的客户端ip
*/
fmt.Println("Run_verify")
appId := "fb6d598f6f2911728e76ba2e60ca9ae0" //和前端验证码的appId保持一致,在顶象的应用配置里有
appSecret := "cfc89571ec179ef809fe7f29bf1c7be3" //appSecret为秘钥,在顶象的应用配置里有
captchaClient := captcha_client.NewCaptchaClient(appId, appSecret) //构建一个用户对象
captchaClient.SetTimeout(2000) //设置超时时间,单位毫秒
captchaClient.SetCaptchaUrl("https://xx.dingxiang-inc.com/api/tokenVerify") //指定服务器地址,saas可在控制台,应用管理页面最上方获取
captchaResponse := captchaClient.VerifyToken(token) //token在前端完成验证后可以获取到,随业务请求发送到后台,token有效期为两分钟
//captchaResponse := captchaClient.VerifyTokenAndIP(token, ip)
//针对一些token冒用的情况,除了判断token的合法性还会校验提交业务参数的客户端ip和验证码颁发token的客户端ip是否一致
//fmt.Println(captchaResponse.CaptchaStatus) //确保验证状态是SERVER_SUCCESS,SDK中有容错机制,在网络出现异常的情况会返回通过
//fmt.Println(captchaResponse.Ip) //验证码服务采集到的客户端ip
if captchaResponse.Result { //为真表示验证通过
//此处为验证通过下一步的代码
fmt.Println("done!")
} else { //为假表示验证失败
//此处为阻断该次请求或者进行下一次验证的代码
fmt.Println("fail!")
}
}

5.流程

  • 前端进行人机验证:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
    <meta http-equiv="X-UA-Compatible" content="IE=8">
    <link rel="shortcut icon" href="https://dingxiang-inc.com/favicon.ico">
    <title>{{.title}}</title>
    <script src="https://cdn.dingxiang-inc.com/fe/common/jquery/1.9.1/jquery.min.js"></script>
    <script src="https://cdn.dingxiang-inc.com/fe/common/beautify/1.6.14/beautify.js"></script>
    </head>
    <body>
    <script src="https://cdn.dingxiang-inc.com/ctu-group/captcha-ui/index.js"></script>

    <div id="c1"></div>

    <div class="ln">
    <h2>人机验证</h2>

    <div class="block">
    <div class="eg">
    <div id="demo-embed"></div>
    </div>
    <script>
    var demo_1 = _dx.Captcha(document.getElementById('demo-embed'), {
    appId: 'fb6d598f6f2911728e76ba2e60ca9ae0',
    apiServer: 'cap.dingxiang-inc.com',
    style: 'embed',
    width: 300,
    success: function (token) {
    $.ajax({
    type:"GET",
    url:"http://127.0.0.1:8080/index/bar",
    dataType:"text",
    data: { token: token}
    });
    //window.console && console.log('success, token:', token)
    window.location.href="http://127.0.0.1:8080/index/done"
    }
    })
    </script>
    </div>
    </div>

    <script>
    function getCfg (key) {
    try {
    return localStorage.getItem(key)
    } catch (e) {
    return undefined
    }
    }

    function setCfg (key, val) {
    try {
    localStorage.setItem(key, val)
    } catch (e) {
    }
    }

    var WG_KEY = 'dx-captcha-demo-wg'
    var g_wg = getCfg(WG_KEY) !== '0'

    function updateStateInfo (v) {
    if (!v) {
    _dx.Captcha._clearVID()
    }
    }

    $(document).ready(function () {
    $('pre').each(function () {
    var pre = $(this)
    var s = pre.parent().find('script').html()
    pre.html(js_beautify(s, {indent_size: 2}))
    })

    if (g_wg) {
    $("#switchOn").prop('checked', true)
    } else {
    $("#switchOff").prop('checked', true)
    }


    updateStateInfo(g_wg)
    })

    function updateCheckStatus(obj) {
    var val = !!parseInt(obj.value)
    g_wg = val
    setCfg(WG_KEY, val ? 1 : 0)
    updateStateInfo(val)
    }



    function resetCaptcha (idx) {
    // _dx.Captcha._clearVID()
    if (!g_wg) {
    _dx.Captcha.getByIdx(idx).reload()
    }
    }

    (function () {
    var i
    for (i = 1; i <= 4; i++) {
    (function (i2) {
    var cpt = _dx.Captcha.getByIdx(i2)
    if (!cpt) {
    setTimeout(arguments.callee, 100, i2)
    return
    }
    cpt.on('verifySuccess', function () {
    setTimeout(function () {resetCaptcha(i2)}, 3000)
    if (!g_wg) {
    _dx.Captcha._clearVID()
    }
    })
    })(i)
    }
    })()
    </script>

    <script src="https://cdn.dingxiang-inc.com/ctu-group/captcha-ui/index.js"></script>
    </body>
    </html>
  • 后端根据token和客户端ip调顶象写好的api进行验证
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package main

    import (
    "Captcha_Project/captcha-client"
    "fmt"
    "github.com/gin-gonic/gin"
    )

    func main(){
    router := gin.Default()
    router.LoadHTMLGlob("tmp/*")
    router.GET("/index", show_index)
    router.GET("/index/done", show_done)
    router.GET("/index/bar", response)
    router.Run()
    }

    func show_index(c *gin.Context){
    fmt.Println("show_index")
    c.HTML(200, "index.html", gin.H{"title": "测试"})
    }

    func show_done(c *gin.Context){
    fmt.Println("show_done")
    c.HTML(200, "done.html", gin.H{"title": "done"})
    }

    func response(c *gin.Context){
    fmt.Println("response")
    token := c.Query("token")
    fmt.Println(token)
    Run_verify(token)
    }

    func Run_verify(token string) {
    /*
    参数token在前端完成验证后可以获取到,随业务请求发送到后台,有效期为两分钟
    参数ip(string)可选,提交业务参数的客户端ip
    */
    fmt.Println("Run_verify")
    appId := "fb6d598f6f2911728e76ba2e60ca9ae0" //和前端验证码的appId保持一致,在顶象的应用配置里有
    appSecret := "cfc89571ec179ef809fe7f29bf1c7be3" //appSecret为秘钥,在顶象的应用配置里有
    captchaClient := captcha_client.NewCaptchaClient(appId, appSecret) //构建一个用户对象
    captchaClient.SetTimeout(2000) //设置超时时间,单位毫秒
    captchaClient.SetCaptchaUrl("https://xx.dingxiang-inc.com/api/tokenVerify") //指定服务器地址,saas可在控制台,应用管理页面最上方获取
    captchaResponse := captchaClient.VerifyToken(token) //token在前端完成验证后可以获取到,随业务请求发送到后台,token有效期为两分钟
    //captchaResponse := captchaClient.VerifyTokenAndIP(token, ip)
    //针对一些token冒用的情况,除了判断token的合法性还会校验提交业务参数的客户端ip和验证码颁发token的客户端ip是否一致
    //fmt.Println(captchaResponse.CaptchaStatus) //确保验证状态是SERVER_SUCCESS,SDK中有容错机制,在网络出现异常的情况会返回通过
    //fmt.Println(captchaResponse.Ip) //验证码服务采集到的客户端ip
    if captchaResponse.Result { //为真表示验证通过
    //此处为验证通过下一步的代码
    fmt.Println("done!")
    } else { //为假表示验证失败
    //此处为阻断该次请求或者进行下一次验证的代码
    fmt.Println("fail!")
    }
    }
  • token验证成功跳转到下一页面(一般是登录成功页面)

企业微信代开发

1.自建应用的流程:

  • 获取access_token:
    1
    (get)https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
  • 企业微信向接受者发送信息:
    1
    (post)https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN

2.参数介绍

  • corpid:每个企业都拥有唯一的corpid,获取此信息可在管理后台“我的企业”-“企业信息”下查看“企业ID”(需要有管理员权限)
  • userid:每个成员都有唯一的userid,即所谓“帐号”。在管理后台->“通讯录”->点进某个成员的详情页,可以看到。
  • external_userid:企业外部联系人的id,可能是微信用户,也可能是企业微信用户。需要开发者(尤其是第三方开发者)注意的是,对于同一个外部联系人,不同调用方(企业/第三方服务商)获取到的external_userid是不同的。
  • agentid:每个应用都有唯一的agentid。在管理后台->“应用与小程序”->“应用”,点进某个应用,即可看到agentid。
  • secret:secret是企业应用里面用于保障数据安全的“钥匙”,每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret务必不能泄漏。

3.用户登录授权

  • 自建应用
    1
    2
    3
    4
    5
    6
    7
    8
    https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect

    # appid企业的CorpID
    # redirect_uri是授权后重定向的回调链接地址,请使用urlencode对链接进行处理
    # response_type是返回类型,此时固定为:code
    # scope是应用授权作用域。企业自建应用固定填写:snsapi_base
    # state是重定向后会带上state参数,企业可以填写a-zA-Z0-9的参数值,长度不可超过128个字节,非必须
    # #wechat_redirect是终端使用此参数判断是否需要带上身份信息
  • 代开发应用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&agentid=AGENTID&state=STATE#wechat_redirect

    # appid是第三方应用id(即ww或wx开头的suite_id)。注意与企业的网页授权登录不同
    # redirect_uri是授权后重定向的回调链接地址,请使用urlencode对链接进行处理 ,注意域名需要设置为第三方应用的可信域名
    # response_type是返回类型,此时固定为:code
    # scope是应用授权作用域。snsapi_base:静默授权,可获取成员的基础信息(UserId与DeviceId);snsapi_userinfo:静默授权,可获取成员的详细信息,但不包含头像、二维码等敏感信息;snsapi_privateinfo:手动授权,可获取成员的详细信息,包含头像、二维码等敏感信息。
    # agentid是企业应用的id。当scope是snsapi_userinfo或snsapi_privateinfo时,该参数必填注意redirect_uri的域名必须与该应用的可信域名一致。
    # state是重定向后会带上state参数,企业可以填写a-zA-Z0-9的参数值,长度不可超过128个字节
    # #wechat_redirect是固定内容

4.流程

  • 企业用户扫码授权,会向这个地址发起一个Post请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    https://127.0.0.1/suite/receive?msg_signature=3a7b08bb8e6dbce3c9671d6fdb69d15066227608&timestamp=1403610513&nonce=380320359

    # 包含的信息体是
    <xml>
    <SuiteId><![CDATA[ww4asffe9xxx4c0f4c]]></SuiteId>
    <AuthCode><![CDATA[AUTHCODE]]></AuthCode>
    <InfoType><![CDATA[create_auth]]></InfoType>
    <TimeStamp>1403610513</TimeStamp>
    <State><![CDATA[123]]></State>
    </xml>
  • 用得到的AuthCode去向以下网址发起Post请求
    1
    2
    3
    4
    5
    https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=SUITE_ACCESS_TOKEN

    {
    "auth_code": "xx"
    }
    • 会返回以下信息
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      # permanent_code就是自建应用用的secret

      {
      "errcode":0,
      "errmsg":"ok",
      "access_token": "xxxxxx",
      "expires_in": 7200,
      "permanent_code": "xxxx",
      "dealer_corp_info":
      {
      "corpid": "xxxx",
      "corp_name": "name"
      },
      "auth_corp_info":
      {
      "corpid": "xxxx",
      "corp_name": "name",
      "corp_type": "verified",
      "corp_square_logo_url": "yyyyy",
      "corp_user_max": 50,
      "corp_full_name":"full_name",
      "verified_end_time":1431775834,
      "subject_type": 1,
      "corp_wxqrcode": "zzzzz",
      "corp_scale": "1-50人",
      "corp_industry": "IT服务",
      "corp_sub_industry": "计算机软件/硬件/信息服务"
      },
      "auth_info":
      {
      "agent" :
      [
      {
      "agentid":1,
      "name":"NAME",
      "round_logo_url":"xxxxxx",
      "square_logo_url":"yyyyyy",
      "appid":1,
      "auth_mode":1,
      "is_customized_app":false,
      "privilege":
      {
      "level":1,
      "allow_party":[1,2,3],
      "allow_user":["zhansan","lisi"],
      "allow_tag":[1,2,3],
      "extra_party":[4,5,6],
      "extra_user":["wangwu"],
      "extra_tag":[4,5,6]
      },
      "shared_from":
      {
      "corpid":"wwyyyyy",
      "share_type": 1
      }
      },
      {
      "agentid":2,
      "name":"NAME2",
      "round_logo_url":"xxxxxx",
      "square_logo_url":"yyyyyy",
      "appid":5,
      "shared_from":
      {
      "corpid":"wwyyyyy",
      "share_type": 0
      }
      }
      ]
      },
      "auth_user_info":
      {
      "userid":"aa",
      "open_userid":"xxxxxx",
      "name":"xxx",
      "avatar":"http://xxx"
      },
      "register_code_info":
      {
      "register_code":"1111",
      "template_id":"tpl111",
      "state":"state001"
      },
      "state":"state001"
      }

目录

直接部署

使用docker和Nginx部署

直接部署

1
2
3
4
5
6
7
前期准备
1.git网站上建库
2.在本地库上初始化git init
3.在本地库上添加所有文件git add .
4.在本地库上初次提交git commit -m "first commit"
5.关联远程库git remote add origin https://gitee.com/kenway20/seckill.git
6.推送到远程库master分支git push -u origin "master"

1.服务器初次部署需要安装go环境

1
apt install golang-go

2.设置云服务器安全组开放入方向的对应端口

3.拉取项目

  • 云服务器执行以下命令切换到工作目录
    1
    cd /home/gowork/gopath
  • clone项目
    1
    2
    3
    git clone xx

    # 默认拉master,若想拉取名为dev的分支用 git clone -b dev xx
  • 进入到项目地址
    1
    cd alumni_platform
  • 安装项目依赖
    1
    2
    3
    4
    go mod tidy

    # 若出现 Get https://proxy.golang.org/github.com/%21puerkito%21bio/goquery/@v/v1.8.0.mod: dial tcp 172.217.163.49:443: i/o timeou这种错误,则说明代理超时
    # 使用go env -w GOPROXY=https://goproxy.cn切换代理后再重新执行
  • 运行项目
    1
    go run cmd/user_web_api/main.go

4.此时就可以通过“主机ip:端口号/xx”来使用接口了

5.后续直接在项目里使用git pull更新,若不想每次都输入账号密码则使用以下命令开启全局git设置

1
2
3
4
git config --global credential.helper store

# 开启这个全局设置后,会在.gitconfig文件中多加[credential]-helper = store,这样只需第一次输入账号和密码就行
# 此时每个项目都有一个.git-credentials文件来记录账号信息

使用Docker和Nginx部署

Docker相关

1.安装docker步骤见本站文章“【工具】Docker的使用”

2.编写Dockerfile并放到项目根目录中(一定要放到根目录下才能执行),以下是一个Dockerfile的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 从官方仓库中获取 1.17 的 Go 基础镜像
FROM golang:1.17-alpine AS builder

# 设置工作目录
WORKDIR /app

# 设置Go使用module、使用代理和Gin采用上线发布模式(还有debug和test模式)
ENV GO111MODULE=on GOPROXY=https://goproxy.cn,direct GIN_MODE=release

# 切换路径
RUN cd

# 编译镜像时把./app的文件复制到镜像里面
COPY . /app

# 下载go依赖的库
# 1.17版本以下的话要用 go mod download
RUN go mod tidy

# 切换到main文件所在目录,并且用ms_user这个父目录名字作为docker的目标
RUN cd /app/cmd/user_web \
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o ms_user

# 获取镜像
FROM alpine

# 把上一轮go build编译获得的二进制文件复制到本阶段中,第一行是main文件的路径
COPY --from=builder /app/cmd/user_web/ .
COPY --from=builder /app/internal/configs/config.json ./internal/configs/config.json

# 设置监听的端口号,这个要与你的Gin项目使用的端口号一致
EXPOSE 8080

# 配置启动命令
ENTRYPOINT ["./ms_user"]

3.在服务器上,cd目录到Dockerfile的路径下执行

1
2
3
docker build -t user_web:v1.1 .

# 表示生成一个名为user_web并且tag为v1.1的镜像

4.清理无用镜像

1
2
3
4
5
6
7
8
# 停止容器
docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }')

# 删除容器
docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }')

# 删除镜像
docker rmi $(docker images | grep "none" | awk '{print $3}')

5.启动镜像

1
2
3
docker run -p 8080:8080 -d --name user_web  user_web:v1.1

# 表示用本服务器的8080端口映射容器的8080端口,将运行镜像名为user_web运行的版本为v1.1,运行的容器命名为user_web

6.查看镜像和容器运行状态

1
2
3
4
5
docker images

docker ps

# 若status出现up xx则表示运行正常,已运行xx时间

7.记得在安全组规则添加开放云服务器的8080端口

8.此时就可以用云服务器公网ip地址:8080/xx来使用接口了

9.删除镜像

1
2
3
4
5
6
7
8
# 停止容器
docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }')

# 删除已经停止的容器
docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }')

# 删除镜像
docker rmi [imageId]

10.一键部署Docker的shell脚本

  • build_user_web.sh
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #! /bin/bash

    echo "first sh statrt"

    # 停止正在运行的容器
    docker stop $(docker ps -a | grep "Up" | awk '{print $1 }')

    # 删除停止的容器
    docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }')

    # 删除名为"user_web"的镜像
    docker rmi $(docker images | grep "user_web" | awk '{print $3}')

    # 切换到my_sense项目路径
    cd /home/gowork/gopath/my_sense

    # 用该目录的Dockerfile打包成镜像user_web,版本号设为v1.1
    docker build -t user_web:v1.1 .

    # 运行user_web:v1.1镜像,生成实例容器user_web
    docker run -p 8080:8080 -d --name user_web user_web:v1.1

    # 删除打包过程中产生的中间镜像<none>
    docker rmi $(docker images | grep "none" | awk '{print $3}')

    echo "first sh end"

定时运行脚本

1.编辑crontab文件,用来存放你要执行的命令(如果是第一次使用该命令还需选打开方式,建议选vim.basic)

1
sudo crontab -e

2.在# m h dom mon dow command这一行下添加要执行的命令,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
0 9 * * * /home/katyusha/code/sendemail.py

# 表示每天的早上9.00执行sendemail.py
# * * * * * command
# 分 时 天 月 周 命令
# 分钟 0 - 59
# 小时 0 - 23
# 天 1 - 31
# 月 1 - 12
# 星期 0 - 6,0表示星期天
# * (星号) 表示任意值,比如在小时部分填写 * 代表任意小时(每小时)
# ,(逗号) 可以允许在一个部分中填写多个值,比如在分钟部分填写 1,3 表示一分钟或三分钟
# /(斜线) 一般配合 * 使用,代表每隔多长时间,比如在小时部分填写 /2 代表每隔两分钟。所以 /1 和 * 没有区别

3.重启cron来应用这个计划任务

1
sudo service cron restart

4.一些命令实例

  • 30 20 * * * date >> /home/date.log //每天的20:30将日期信息追加home目录下的date.log中
  • 5 20 13,14 * * date >> /home/date.log //每月的13日,14日20:5分将日期信息最追加home目录下的date.log中
  • 0,10 10,12 * * * date >> /home/date.log 每天10点至12点之间,隔10分钟将日期信息最追加home目录下的date.log中
  • /5 * * * date >> /home/date.log //每5分钟将日期信息最追加home目录下的date.log中

5.查看cron是否在运行

1
ps -ef | grep cron

6.操作cron服务

1
2
3
4
crontab -l              //查看设置的所有定时任务,其实就是只读方式打开
service crond start //启动服务
service crond stop //关闭服务
service crond restart //重启服务

目录

Mysql数据库

Redis数据库

Mysql数据库

Ubuntu安装mysql

1.依次执行下述命令更新软件包和安装mysql

1
2
3
4
5
# 更新软件包列表
sudo apt update

# 安装mysql
sudo apt-get install mysql-server mysql-client

2.初始化配置msql规则

  • 执行下述语句
    1
    sudo mysql_secure_installation
  • Enter current password for root (enter for none):<–初次运行直接回车
  • xxx<-是否安装验证密码插件,建议No,因为安装后会限制简单密码的设置
  • Set root password? [Y/n] <– 是否设置root用户密码,输入y并回车或直接回车
  • New password: <– 设置root用户的密码
  • Re-enter new password: <– 再输入一次你设置的密码
  • Remove anonymous users? [Y/n]<–是否删除匿名用户,生产环境建议删除,所以直接回车
  • Disallow root login remotely? [Y/n] <–禁止root远程登录?根据自己的需求选择Y/n并回车
  • Remove test database and access to it? [Y/n] <– 是否删除安装时自动创建的test数据库,直接回车(根据个人需求)
  • Reload privilege tables now? [Y/n] <– 是否重新加载权限表,直接回车

mysql配置

1.配置mysql权限

  • 进入mysql环境
    1
    mysql -u root -p
  • 创建远程连接的用户
    1
    2
    3
    4
    mysql>CREATE USER 'root'@'%' IDENTIFIED BY '123456';

    # '%'表示任意ip地址都可以用这个用户访问
    # root是用户名,123456是密码
  • 给这个用户赋予所有操作的权限
    1
    mysql>GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
  • 刷新
    1
    mysql>FLUSH PRIVILEGES;
  • 退出到控制台并重启mysql
    1
    2
    3
    mysql>quit;

    service mysql start

2.允许远程连接

  • 修改/etc/mysql/mysql.conf.d/mysqld.cnf,将bind-address = 127.0.0.1前面加# 注释掉
    1
    vim /etc/mysql/mysql.conf.d/mysqld.cnf

远程连接

1.Navicat配置ip(云服务器公网ip)、用户名、密码和端口3306后即可连接

可能会出现的问题

1.mysql授权时使用以下语句报错sql语句不合法

1
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'password'WITH GRANT OPTION;
  • 原因:在Mysql 8版本必须分两步来实现设置用户权限【先创建用户、在对该用户分配用户权限】,最后刷新权限
  • 解决办法:先创建,再分配权限即可
    1
    2
    mysql>CREATE USER 'root'@'%' IDENTIFIED BY '123456';
    mysql>GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;

2.远程连接报10038错误

  • 原因:云服务器3306端口未开放
  • 解决办法:安全组设置,然后重启云服务器
    • 优先级:1
    • 协议类型:自定义TCP
    • 端口范围:3306/3306
    • 授权对象:0.0.0.0/0

3.远程连接报1251错误

  • 原因:mysql 8采用另一种密码加密方式
  • 解决办法:把root用户的密码加密方式改成老版本(另一种方法是更新Navicat)
    1
    2
    3
    4
    5
    mysql>SELECT host,user,plugin,authentication_string From mysql.user;

    mysql>ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

    mysql>FLUSH PRIVILEGES;
    • 如果修改密码的时候报failed for ‘root‘@’%’错误,那么还需要依次执行以下语句
      1
      2
      3
      mysql>update mysql.user set Host='%' where User='root';

      mysql>flush privileges;

4.远程连接报1045错误

  • 原因:远程没有权限或密码错误
  • 解决办法:更改mysql设置的远程用户的密码
    1
    ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

Redis数据库

Ubuntu安装Redis

1.依次执行下述命令更新软件包和安装Redis

1
2
3
4
5
# 更新软件包列表
sudo apt update

# 安装Redis
sudo apt-get install redis-server

2.配置文件

  • 打开配置文件
    1
    sudo vim /etc/redis/redis.conf
  • 添加密码
    1
    2
    3
    4
    requirepass 123456

    # 表示密码为123456,如果把requirepass注释掉就说明不需要密码
    # 如果不设密码的话就要把protected-mode改成no
  • 开启Redis的远程连接
    1
    2
    3
    4
    #bind 127.0.0.1

    # 把这行注释掉,不然默认绑定本地连接
    # 或者改成bind 0.0.0.0表示绑定本机上的所有ip地址
  • 更改端口
    1
    port 6379
  • 保存配置后重启Redis
    1
    sudo service redis-server restart

3.使用以下命令可以看Redis运行的是哪个端口

1
ps -aux|grep redis

4.
使用以下命令可以建立一个交互式连接

1
2
3
redis-cli -h 127.0.0.1 -p 6379

# 如果设置了密码要用这个命令输入密码auth "123456"

可能会出现的问题

1.No connection could be made because the target machine actively refused it

  • 原因:Redis拒绝远程访问
  • 注释掉配置文件的bind 127.0.0.1并设置密码,或注释掉配置文件的bind 127.0.0.1并关闭protected-mode

2.NOAUTH Authentication required.

  • 原因:Redis设置了密码
  • 解决办法:用这个命令输入密码auth “xx”

3.云服务器上的端口开放了,但是远程连接超时

  • 原因:安全组未开放,c或s有一端的防火墙设置
  • 解决办法:开放安全组,防火墙设置开放该端口,或者换热点(某些网络会自动墙掉他认为不安全的地址)