Skip to content

Commit

Permalink
1:优化了程序
Browse files Browse the repository at this point in the history
2:支持统一时区设置(docker部署需要用到)
 Changes to be committed:
	modified:   README.md
	modified:   apiserver.go
	new file:   apiserver_test.go
	modified:   config.go
	modified:   config_test.go
	modified:   const.go
	modified:   func.go
	modified:   main.go
	modified:   model.go
	modified:   sms.go
	new file:   zoneinfo.zip
  • Loading branch information
xluohome committed Mar 26, 2017
1 parent 9734bbe commit 10191c5
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 106 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o smscode .
FROM scratch
ADD smscode /
COPY etc/* /etc/
COPY conf/* /conf/
CMD ["/smscode"]
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,37 @@ SmsCode

./build && ./smscode

### Docker 部署Smscode
### Docker部署

请参考项目中的Dockerfile 制作Docker image。

### 时区设置

请修改 conf.yaml 中的 timezone 参数。

timezone 具体值请查考 conf/zoneinfo.zip

### 功能特性

1. 支持阿里大鱼、云通讯等多个“手机短信验证码”(以下简称:验证码)通道;
2. 自定义多个验证码服务接口,如:新用户注册,重设密码,身份验证等等;
3. 内置手机号归属地限制,可限制指定归属地手机号接收验证码;
4. 每个验证码服务可设置验证码每日发送数量限额及失效时间;
5. 内置callback服务,可设置验证码发送成功(失败)、验证码验证成功时的回调URL;
3. 内置手机号归属地限制,限制指定归属地手机号可接收验证码;
4. 每个验证码服务可设置“验证码”每日最大发送数量限额及有效时间;
5. 内置callback服务,可设置验证码发送成功(失败)、验证码验证成功时的回调http Url;
6. 可设置验证码发送模式:
- 0x01:只有手机号对应的uid存在时才能发送。
- 0x02:只有uid不存在时才能发送。
- 0x03:不管uid是否存在都发送。
7. 通过setuid接口可将现有系统中的用户UID数据导入SmsCode;
8. 内置持久化存储:Goleveldb;
8. 内置本地持久化存储:Goleveldb;
9. 支持Docker部署,SmsCode静态编译(Go 1.7.5)Docker image不到8mb(不含归属地数据库);

### 配置文件 etc/conf.yaml
### 配置文件 conf/conf.yaml

```
bind: 0.0.0.0:8080 #短信验证码微服务器地址
timezone: PRC #时区设置
timeout: 5 #短信供应商网关响应超时时间(秒)
vendors:
alidayu: #阿里大鱼配置 http://www.alidayu.com
appkey: 20315570
Expand Down Expand Up @@ -190,13 +198,13 @@ servicelist:

### Callback 服务

每个短信验证码服务允许设置一个callback Url ;
如下事件发生时将回调callback Url
每个短信验证码服务允许设置一个callback http Url ;
如下事件发生时将回调callback http Url

1. 手机验证码发送成功或者失败时。
2. 手机验证码通过验证时。

当 callback Url无法访问时,系统会延时2,4,6,8,16,32,64,128秒 依次进行重试(合计10次)。
当 callback http Url无法访问时,系统会延时2,4,6,8,16,32,64,128秒 依次进行重试(合计10次)。

成功时附带如下Url POST请求参数(multipart/form-data):
```
Expand Down
124 changes: 79 additions & 45 deletions apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,103 @@ import (
"encoding/json"
"fmt"
"net/http"
"time"

log "github.com/golang/glog"
)

type apiserver struct {
req []chan *request
result []chan Result
}

func (apiserver) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
log.Info(r.RemoteAddr, r.URL)
if err := recover(); err != nil {
var msg = fmt.Sprintf("服务器错误:%s", err)
log.Error(msg)
w.WriteHeader(500)
w.Write([]byte(msg))
return
}
}()

var err error
var infos interface{}
type request struct {
Act, Mobile, Code, Service, Uid string `json:",omitempty"`
}

mobile := r.FormValue("mobile")
code := r.FormValue("code")
serviceName := r.FormValue("service")
uid := r.FormValue("uid")
func (r *request) String() string {
return fmt.Sprintf("%s\t%s\t%s\t%s\t%s", r.Act, r.Mobile, r.Code, r.Service, r.Uid)
}
func (api *apiserver) sms(i int) {

var (
infos interface{}
err error
result Result
)
sms := NewSms()
sms.SetServiceConfig(serviceName)

switch r.URL.String() {
case "/send":
err = sms.Send(mobile)
case "/checkcode":
err = sms.CheckCode(mobile, code)
case "/setuid":
err = sms.SetUid(mobile, uid)
case "/deluid":
err = sms.DelUid(mobile, uid)
case "/info":
infos, err = sms.Info(mobile)
default:
err = fmt.Errorf("%s", "您访问的api不存在")
}
for req := range api.req[i] {

var result Result
sms.NowTime = time.Now()
sms.SetServiceConfig(req.Service)

infos = nil

switch req.Act {
case "send":
err = sms.Send(req.Mobile)
case "checkcode":
err = sms.CheckCode(req.Mobile, req.Code)
case "setuid":
err = sms.SetUid(req.Mobile, req.Uid)
case "deluid":
err = sms.DelUid(req.Mobile, req.Uid)
case "info":
infos, err = sms.Info(req.Mobile)
default:
err = fmt.Errorf("%s\t%s", "API not found:", req.Act)
}

switch sms.Config.Outformat {
case "mobcent":
result = &Result_mobcent{}
default:
result = &Result_default{}
}

switch sms.Config.Outformat {
case "mobcent":
result = &Result_mobcent{}
default:
result = &Result_default{}
result.Format(err, infos)
api.result[i] <- result
}
return
}

//对象输出格式化
result.Format(err, infos)
func (api *apiserver) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Info(r.RemoteAddr, r.URL)
defer func() {
if e := recover(); e != nil {
err := fmt.Errorf("Server error :%s", e)
w.WriteHeader(500)
w.Write([]byte(err.Error()))
}
}()

//json对象
str, _ := json.Marshal(result)
req := request{r.URL.String()[1:], r.FormValue("mobile"), r.FormValue("code"), r.FormValue("service"), r.FormValue("uid")}
hash := hashFunc([]byte(req.String())) & uint64(*smsworks-1)
api.req[hash] <- &req

var result Result
select {
case result = <-api.result[hash]:
case <-time.After(config.TimeOut * time.Second):
panic("Response timeout error!")
}
w.Header().Set("Content-Type", ContentType)
w.Header().Set("TimeZone", time.Local.String())
str, _ := json.Marshal(result)
w.Write(str)
}

func Apiserver() {
log.Fatal(http.ListenAndServe(config.Bind, &apiserver{}))
var api = new(apiserver)
api.req = make([]chan *request, *smsworks)
api.result = make([]chan Result, *smsworks)
for i := 0; i < *smsworks; i++ {
go func(i int) {
api.req[i] = make(chan *request)
api.result[i] = make(chan Result)
api.sms(i)
}(i)
}

log.Fatal(http.ListenAndServe(config.Bind, api))
}
114 changes: 114 additions & 0 deletions apiserver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package main

import (
"io/ioutil"
"net/http"
"net/url"
"testing"
)

func TestApiserver(t *testing.T) {

if e := recover(); e != nil {
t.Error(e)
}

config.Bind = "0.0.0.0:8080"
go func() {
Apiserver()
}()

t.Run("Send", testsend)
t.Run("Checkcode", testcheckcode)
t.Run("Setuid", testsetuid)
t.Run("Deluid", testdeluid)
t.Run("Info", testinfo)
}

func testsend(t *testing.T) {
apiurl := "http://" + config.Bind
var data = url.Values{}
data.Set("mobile", "13575566313")
data.Set("service", "register")
res, err := http.DefaultClient.PostForm(apiurl+"/send", data)
if err != nil {
t.Error(err)
}
defer res.Body.Close()
cont, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
t.Log(string(cont))
}

func testcheckcode(t *testing.T) {
apiurl := "http://" + config.Bind
var data = url.Values{}
data.Set("mobile", "13575566313")
data.Set("service", "register")
data.Set("code", "333333")
res, err := http.DefaultClient.PostForm(apiurl+"/checkcode", data)
if err != nil {
t.Error(err)
}
defer res.Body.Close()
cont, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
t.Log(string(cont))
}

func testsetuid(t *testing.T) {
apiurl := "http://" + config.Bind
var data = url.Values{}
data.Set("mobile", "13575566313")
data.Set("service", "register")
data.Set("uid", "1")
res, err := http.DefaultClient.PostForm(apiurl+"/setuid", data)
if err != nil {
t.Error(err)
}
defer res.Body.Close()
cont, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
t.Log(string(cont))
}

func testdeluid(t *testing.T) {
apiurl := "http://" + config.Bind
var data = url.Values{}
data.Set("mobile", "13575566313")
data.Set("service", "register")
data.Set("uid", "1")
res, err := http.DefaultClient.PostForm(apiurl+"/deluid", data)
if err != nil {
t.Error(err)
}
defer res.Body.Close()
cont, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
t.Log(string(cont))
}

func testinfo(t *testing.T) {
apiurl := "http://" + config.Bind
var data = url.Values{}
data.Set("mobile", "13575566313")
data.Set("service", "register")
res, err := http.DefaultClient.PostForm(apiurl+"/info", data)
if err != nil {
t.Error(err)
}
defer res.Body.Close()
cont, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
t.Log(string(cont))
}
3 changes: 3 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package main

import (
"io/ioutil"
"time"

"gopkg.in/yaml.v2"
)

type Config struct {
Bind string `yaml:"bind"`
Timezone string `yaml:"timezone"`
TimeOut time.Duration `yaml:"timeout"`
Vendors map[string]map[string]string `yaml:"vendors"`
Juheapikey string `yaml:"juheapikey"`
ServiceList map[string]*ServiceConfig `yaml:"servicelist"`
Expand Down
2 changes: 1 addition & 1 deletion config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestGetConfig(t *testing.T) {

var conf Config

err := conf.ParseConfigFile("etc/conf.yaml")
err := conf.ParseConfigFile("conf/conf.yaml")

if err != nil {

Expand Down
2 changes: 1 addition & 1 deletion const.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

const (
VERSION = "0.13 beta"
VERSION = "0.14 beta"
ContentType = "text/json"
CacheSize int = 64
WriteBuffer int = 32
Expand Down
6 changes: 6 additions & 0 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ import (
"regexp"
"strconv"
"time"

"github.com/spaolacci/murmur3"
)

func hashFunc(data []byte) uint64 {
return murmur3.Sum64(data)
}

//生成验证码
func makeCode() (code string) {
code = strconv.Itoa(rand.New(rand.NewSource(time.Now().UnixNano())).Intn(899999) + 100000)
Expand Down
Loading

0 comments on commit 10191c5

Please sign in to comment.