文档章节

我的第一个go程序(四)

huangcx
 huangcx
发布于 2016/01/31 10:02
字数 1383
阅读 6
收藏 0
点赞 1
评论 0

就快要过年了,祝大家在新的一年里有新的收获!

下面以一个发送短信的模块为例子,注:具体发送短信是通过调用第三方接口完成的,我们只需要将短信成功发送到接口即可,可根据接口实际情况查询或返回短信发送情况(发送成功与否)。

每个模块相互独立,都有自己的配置信息,因此设置常量:

const (
    moduleCode  = "sms.send"   // 模块标识,固定值
    configCode  = "sms.zt.api" // 相关参数配置标识,固定值
    maxExecuted = 5            // 限制失败次数
)

设置系统中必要的变量

var (
    dsnBasic   string            // Basic 数据库dsn
    dsnMsg     string            // Msg 数据库dsn
    dbBasic    *sql.DB           // 数据库对象
    dbMsg      *sql.DB           // 数据库对象
    taskModule *basic.TaskModule // 模块结构
    taskConfig *basic.Config     // 业务配置结构
    stopped    = false           // 停止标志
    stopMutex  *sync.RWMutex     // 停止标志锁
    limit      = 50              // 每次获取任务数量
    sleepTime  = 1               // 间隔N秒获取一次
    wokers     = runtime.NumCPU()
)

var (
    apiGateway   string // 接口URL
    apiUsername  string // 接口用户名
    apiPassword  string // 接口密码
    apiProductId string // 接口产品ID
)

模块要设置、检测停止状态,增加一个获取以及设置停止标志的函数:

// 获取是否停止标志
func isStop() bool {
    //这里只是读取值,加个锁反而觉得是多余的
    stopMutex.RLock()
    defer stopMutex.RUnlock()
    return stopped
}
// 设置停止标志
func stop() {
    stopMutex.Lock()
    defer stopMutex.Unlock()
    stopped = true
}

模块初始化时设置相应的变量值:

// 初始化
func init() {
    var err error
    // 加载配置文件
    c, _ := config.Read("app.cfg", "# ", "=", false, false)
    // 读取数据库配置
    dsnBasic, _ = c.String("database", "dsnBasic")
    dsnMsg, _ = c.String("database", "dsnMsg")
    // 两个数据库操作对象
    if dbMsg, err = sql.Open("mysql", dsnMsg); err != nil {
        logger.Error().Panicf("数据库[%s]异常:%s\n", dsnMsg, err)
    }
    // 设置最大连接数
    dbMsg.SetMaxOpenConns(wokers)
    dbMsg.SetMaxIdleConns(wokers - 1)

    if dbBasic, err = sql.Open("mysql", dsnBasic); err != nil {
        logger.Error().Panicf("数据库[%s]异常:%s\n", dsnBasic, err)
    }
    // 获取模块信息
    if taskModule, err = basic.GetTaskModule(dbBasic, moduleCode); err != nil {
        logger.Error().Panicf("获取模块[%s]异常:%s\n", moduleCode, err)
    }
    // 获取业务相关配置
    if taskConfig, err = basic.GetConfig(dbBasic, configCode); err != nil {
        logger.Error().Panicf("获取配置[%s]异常:%s\n", configCode, err)
    }

    // 获取接口配置
    var configData map[string]interface{}
    if err := json.Unmarshal([]byte(taskConfig.JsonData), &configData); err != nil {
        logger.Error().Panicf("获取配置节点[%s:JsonData]异常:%s\n", configCode, err)
    }
    apiGateway = configData["gateway"].(string)
    apiUsername = configData["username"].(string)
    apiPassword = configData["password"].(string)
    apiProductId = configData["product_id"].(string)

    // 锁对象
    stopMutex = new(sync.RWMutex)
}

为这个模块取个名字:sms,并实现Tasker接口

// 发送短信结构,实现 task.Tasker接口
type sms struct {
    done  chan struct{}
    tasks chan *task.Task
}

// 短信内容结构
type smsContent struct {
	id       string         // 内容ID
	content  string         // 短信内容
	sendTime string         // 待发送时间
	extCode  sql.NullString // 扩展编号
}

这个结构里的done 通道,是检测到需要停止执行的时候,往done通道发送信号,外部接收到后,表示模块正常结束,tasks 是需要执行的任务通道,可通过多线程执行并行获取通道里的任务执行。

smsContent 则保存了短信的具体内容,略。

sms 要实现Tasker接口,则需要实现相应的 Start 与 Stop 方法

// 外部调用Start方法开始执行
func (tasker *sms) Start() {
    go tasker.started()
    // 开启多个工作线程
    // 需要注意以及解决的问题
    // tasks通道未设置缓冲区,是通过阻塞避免重复提取相同数据,但最后一条或N(线程数)条数据还是有重复的可能:
    // 如:最后一条数据的状态在数据库中还未更新,线程又从数据库批量获取任务时,会导致重复获取
    for i := 0; i < wokers; i++ {
        go tasker.processTask(i)
    }
}
// 外部调用Stop方法停止执行
// 等待所有工作线程结束,并关闭数据库连接
func (tasker *sms) Stop(done chan<- task.Done) {
    stop()
    // 等待所有工作线程结束
    for i := 0; i < wokers; i++ {
        <-tasker.done
    }
    // 关闭数据库连接
    dbBasic.Close()
    dbMsg.Close()
    // 发送模块完成信号
    done <- task.Done{ModuleCode: taskModule.Code, ModuleName: taskModule.Name}
}
// 无限循环方法
func (tasker *sms) started() {
    // 开启一个无限循环
    logger.Debug().Printf("模块[%s]开始运行", moduleCode)
    for {
        if isStop() {
            break
        }
        // 获取任务
        tasks, err := task.GetTasks(dbMsg, taskModule, limit, maxExecuted)
        //tasks, err := getTasksTest(dbMsg, taskModule, limit)
        if err != nil {
            logger.Error().Printf("获取模块[%s]任务错误:%s\n", taskModule.Code, err)
        }
        for _, t := range tasks {
            if isStop() {
                break
            }
            tasker.tasks <- t
        }
        // 休眠一段时间
        time.Sleep(time.Duration(sleepTime) * time.Second)
    }
    logger.Debug().Printf("模块[%s]结束运行\n", moduleCode)
    close(tasker.tasks)
}

//实际业务处理,参数 i没有任何意义,测试用,可去掉
func (tasker *sms) processTask(i int) {
    for tk := range tasker.tasks { //进入阻塞
       // 这里执行具体的业务,
       // 可根据实际情况自行设置规则
       _, err := sendToServer(tk)

        if err != nil {
            logger.Error().Printf("执行任务失败[taskid=%s]:%s\n", tk.Id, err.Error())
            // 再次发送
            //执行N次失败后,不再执行
            if tk.ExecCount < maxExecuted {
                nextExecTime := getNextExecTime(int(tk.ExecCount))
                _, err = tk.AddChild(dbMsg, nextExecTime.Format("2006-01-02 15:04:05"), false)
                if err != nil {
                    logger.Error().Printf("增加子任务失败[taskid=%s]:%s\n", tk.Id, err.Error())
                }
            }
        }

        //增加到历史记录并删除
        _, err = tk.AddToHistory(dbMsg)
        if err != nil {
            logger.Error().Printf("增加任务到历史记录失败[taskid=%s]:%s\n", tk.Id, err.Error())
        }
    }
    // 往结束通道发送消息
    tasker.done <- struct{}{}
}
func sendToServer(tk *task.Task) (int, error) {
    //发送信息到接口,略
}
//获取延迟发送时间
func getNextExecTime(n int) time.Time {
    switch {
    case n < 3:
        return time.Now().Add(10 * time.Second) // 1~2次失败则延迟10秒钟
    case n < maxExecuted:
        return time.Now().Add(1 * time.Minute) // 3~4次失败则延迟1分钟
    case n >= maxExecuted:
        return time.Now().Add(30 * time.Minute) //
    }
    return time.Now()
}


sms结构 不对外访问,因此我们通过New方法获取

// 初始化一个Tasker对象
func New() task.Tasker {
    tasker := &sms{done: make(chan struct{}), tasks: make(chan *task.Task)}
    return tasker
}

外部调用方式:

tasker := sms.New()
tasker.Start() 开始执行
tasker.Stop() 停止执行


好,代码差不多了,实现sendToServer方法,基本就可以了。

© 著作权归作者所有

共有 人打赏支持
huangcx
粉丝 0
博文 4
码字总数 7225
作品 0
宁波
程序员
android string.xml %问题String sAgeFormat1 = getR...

在android的开发中,经常会遇见一句话,比如“我今年23岁了”;这个23需要在程序中生成,但是遇到一个问题,这完整的一句话是一个TextView中的,而不是三个textView拼接成的,而且是引用的s...

Koon.LY ⋅ 2012/05/15 ⋅ 0

linux学习(一) linux磁盘相关

一 设备命名 装置 装置在 Linux 内癿文件名 IDE 硬盘机 /dev/hd[a-d] SCSI/SATA/USB硬盘机 /dev/sd[a-p] USB 快闪碟 /dev/sd[a-p](与 SATA 相同) 软盘驱劢器 /dev/fd[0-1] 打印机 25 针: /de...

等待救赎 ⋅ 2016/04/18 ⋅ 0

Android adt bundle 开发环境配置及第一个“Hello world”程序运行

最近在学习Android 顺便记录下学习过程当作复习吧,这是写的第一篇正式博客。 一、jdk环境配置 二、android adt bundle 下载 三、安装SDK 四、模拟器及真机调试 五、第一个程序 Hello world!...

程序猿付显 ⋅ 2014/07/23 ⋅ 0

android中string.xml中%一$s、%1$d等的用法

1、整型,比如“我今年23岁了”,这个23是整型的。在string.xml中可以这样写,<string name="old">我今年%1$d岁了</string> 在程序中,使用 [java] view plain copy String sAgeFormat = get......

青莲居士 ⋅ 2016/04/10 ⋅ 0

%1$s %1$d Android string

1、整型,比如“我今年23岁了”,这个23是整型的。在string.xml中可以这样写,<string name="old">我今年%1$d岁了</string> 在程序中,使用 String sAgeFormat = getResources().getString(R......

carlos ⋅ 2014/08/15 ⋅ 0

ZenTaoPMS新年推出第一个BETA版本

禅道项目管理软件(ZenTaoPMS)是一款国产的,基于LGPL协议,开源免费的项目管理软件,它集产品管理、项目管理、测试管理于一体,同时还包含了事务管理、组织管理等诸多功能,是中小型企业项目...

开源春哥 ⋅ 2010/01/04 ⋅ 3

MFC 界面美化 Skinmagic

用MFC写的这个应用程序,写出来的界面实在是看不下去,于是乎到就像来在界面添加一些图片来美化,于是找了图片,还是太丑,又看到网上的帖子说有皮肤库啊,那个界面更好看,于是搜,也没有什...

种地瓜 ⋅ 2015/12/10 ⋅ 0

2007年我的Blog总结

2007年,我一共写了350多篇网志,差不多一天一篇。 下面是简单的回顾: 2月,重读了《胡适口述自传》,作了详细的笔记。 3月,开始学习图书馆学方面的知识。 4月,想写一组2007年普利策奖的回...

阮一峰 ⋅ 2007/12/31 ⋅ 0

五子棋算法

任何一种棋类游戏其关键是对当前棋局是否有正确的评分,评分越准确则电脑的AI越高。五子棋游戏也是如此,但在打分之前,我们先扫描整个棋盘,把每个空位从八个方向上的棋型填入数组gStyle(2,...

长平狐 ⋅ 2012/08/13 ⋅ 0

makefile 之阶段总结--1--写一个简单的makefile

一. 没有makefile&&make的时候 在没有make&&makefile的时候,我是在IDE中编译程序的.最开始使用命令行编译程序,是在写OJ题目的时候,毕竟OJ题目只需要一个文件,在命令行中编译运行...

小代码2016 ⋅ 2014/11/14 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

mysql in action / alter table

change character set ALTER SCHEMA `employees` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci ;ALTER TABLE `employees`.`t2` CHARACTER SET = utf8mb4 , COLLAT......

qwfys ⋅ 今天 ⋅ 0

Java 开发者不容错过的 12 种高效工具

Java 开发者常常都会想办法如何更快地编写 Java 代码,让编程变得更加轻松。目前,市面上涌现出越来越多的高效编程工具。所以,以下总结了一系列工具列表,其中包含了大多数开发人员已经使用...

jason_kiss ⋅ 昨天 ⋅ 0

Linux下php访问远程ms sqlserver

1、安装freetds(略,安装在/opt/local/freetds 下) 2、cd /path/to/php-5.6.36/ 进入PHP源码目录 3、cd ext/mssql进入MSSQL模块源码目录 4、/opt/php/bin/phpize生成编译配置文件 5、 . ./...

wangxuwei ⋅ 昨天 ⋅ 0

如何成为技术专家

文章来源于 -- 时间的朋友 拥有良好的心态。首先要有空杯心态,用欣赏的眼光发现并学习别人的长处,包括但不限于工具的使用,工作方法,解决问题以及规划未来的能力等。向别人学习的同时要注...

长安一梦 ⋅ 昨天 ⋅ 0

Linux vmstat命令实战详解

vmstat命令是最常见的Linux/Unix监控工具,可以展现给定时间间隔的服务器的状态值,包括服务器的CPU使用率,内存使用,虚拟内存交换情况,IO读写情况。这个命令是我查看Linux/Unix最喜爱的命令...

刘祖鹏 ⋅ 昨天 ⋅ 0

MySQL

查看表相关命令 - 查看表结构    desc 表名- 查看生成表的SQL    show create table 表名- 查看索引    show index from  表名 使用索引和不使用索引 由于索引是专门用于加...

stars永恒 ⋅ 昨天 ⋅ 0

easyui学习笔记

EasyUI常用控件禁用方法 combobox $("#id").combobox({ disabled: true }); ----- $("#id").combobox({ disabled: false}); validatebox $("#id").attr("readonly", true); ----- $("#id").r......

miaojiangmin ⋅ 昨天 ⋅ 0

金山WPS发布了Linux WPS Office

导读 近日,金山WPS发布了Linux WPS Office中文社区版新版本,支持大部分主流Linux系统,功能更加完善,兼容性、稳定性大幅度提升。本次更新WPS将首次在Linux提供专业办公文件云存储服务,实...

问题终结者 ⋅ 昨天 ⋅ 0

springboot2输出metrics到influxdb

序 本文主要研究一下如何将springboot2的metrics输出到influxdb maven <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-bo......

go4it ⋅ 昨天 ⋅ 0

微信小程序 - 选择图片显示操作菜单

之前我分享过选择图片这个文章,但是我在实际开发测试使用中发现一个问题在使用 wx.chooseImage 选择照片显示出第一格是拍照,后面是相册里的图片。这种实现之前说过了,效果如下。 但是你从...

hello_hp ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部