├── .gitignore ├── README.md ├── admin.sh ├── conf └── config.yaml ├── dao ├── account_dao.go └── user_dao.go ├── extra └── schema │ └── demo.sql ├── handler ├── account_handler.go ├── base_handler.go ├── check_handler.go └── user_handler.go ├── main.go ├── model ├── account_model.go ├── base_model.go └── user_model.go ├── pkg ├── api_token.go ├── config.go ├── errno │ ├── code.go │ └── errno.go ├── log │ ├── README.md │ ├── example │ │ ├── example.go │ │ └── log.yaml │ ├── lager.go │ ├── lager │ │ ├── color.go │ │ ├── logger.go │ │ ├── models.go │ │ ├── reconfigurable_sink.go │ │ └── writer_sink.go │ ├── log.yaml │ ├── logrotate.go │ └── st_lager.go ├── logger.go ├── mysql.go ├── redis.go └── redis_test.go ├── router ├── middleware │ ├── auth.go │ ├── header.go │ └── logging.go └── router.go ├── service ├── account_service.go └── user_service.go ├── task └── demo_task.go └── util ├── common_util.go ├── common_util_test.go └── convert_util.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # MacOS temp file 15 | .DS_Store 16 | 17 | # IDE config 18 | .idea 19 | 20 | ### Project specifics ### 21 | logs/ 22 | vendor/ 23 | vc-gin-api -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Server Run 2 | ```shell script 3 | cd $GOPATH/src/vc-gin-api 4 | go build 5 | ./admin.sh start 6 | ``` 7 | 8 | ## 1. Account Login 9 | 10 | **Url** : `http://127.0.0.1:8080/api/account/login` 11 | 12 | **Method** : `POST` 13 | 14 | **Params** : `name`, `password` 15 | 16 | **Auth Required** : `No` 17 | 18 | **Request** : 19 | 20 | ```shell script 21 | curl -X POST -H "Content-Type:application/json" \ 22 | http://127.0.0.1:8080/api/account/login -d'{"name":"admin", "password":"admin123456"}' 23 | ``` 24 | **Response** : 25 | 26 | ``` 27 | { 28 | "code":0, 29 | "msg":"ok", 30 | "data":{ 31 | "name":"admin", 32 | "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTI3NTA4ODIsImlhdCI6MTYxMjc0OTA4MiwiaWQiOjEsIm5iZiI6MTYxMjc0OTA4MiwidXNlcm5hbWUiOiJhZG1pbiJ9.MmmQMd9V596vCbZaXx758bNSKHJdD5_tsD-K-RU3RP8" 33 | } 34 | } 35 | ``` 36 | 37 | ## 2. Account Password Update 38 | 39 | **Url** : `http://127.0.0.1:8080/api/account/updatePass` 40 | 41 | **method** : `POST` 42 | 43 | **Params** : `name`, `oldPwd`, `newPwd` 44 | 45 | **Auth Required** : `No` 46 | 47 | **request** : 48 | 49 | ```shell script 50 | curl -X POST -H "Content-Type:application/json" \ 51 | http://127.0.0.1:8080/api/account/updatePass -d'{"name":"admin", "oldPwd":"admin123456", "newPwd":"admin666666"}' 52 | ``` 53 | **response** : 54 | 55 | ``` 56 | { 57 | "code":0, 58 | "msg":"ok" 59 | } 60 | ``` 61 | 62 | ## 3. Query Users 63 | 64 | **Url** : `http://127.0.0.1:8080/api/user/common/queryAll` 65 | 66 | **Method** : `GET` 67 | 68 | **Params** : `start`, `limit`, `name`, `address` 69 | 70 | **Auth Required** : `No` 71 | 72 | **Request** : 73 | 74 | ```shell script 75 | curl http://127.0.0.1:8080/api/user/common/queryAll 76 | ``` 77 | 78 | **Response** : 79 | 80 | ``` 81 | { 82 | "code":0, 83 | "msg":"ok", 84 | "data":{ 85 | "total":2, 86 | "rows":[ 87 | { 88 | "id":1, 89 | "name":"vince", 90 | "age":24, 91 | "address":"beijing" 92 | }, 93 | { 94 | "id":2, 95 | "name":"join", 96 | "age":25, 97 | "address":"shanghai" 98 | } 99 | ] 100 | } 101 | } 102 | ``` 103 | 104 | ## 4. Add User 105 | 106 | **Url** : `http://127.0.0.1:8080/api/user/auth/add` 107 | 108 | **Method** : `POST` 109 | 110 | **Params** : `name`, `age`, `address` 111 | 112 | **Auth Required** : `Yes` 113 | 114 | **Request** : 115 | 116 | ```shell script 117 | curl -X POST -H "Content-Type:application/json" \ 118 | http://127.0.0.1:8080/api/user/auth/add -d'{"name":"david", "age":28, "address":"shenzhen"}' \ 119 | -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Njk1NTc3NjIsImlhdCI6MTU2OTU1NzE2MiwiaWQiOjEsIm5iZiI6MTU2OTU1NzE2MiwidXNlcm5hbWUiOiJKYW1lcyJ9.-WTzVOqRe6-Ax3M4TpGIACjEqef8PqzcKArLF4_gmRE" 120 | ``` 121 | 122 | **Response** : 123 | ```shell script 124 | { 125 | "code":0, 126 | "msg":"ok", 127 | "data":null 128 | } 129 | ``` 130 | 131 | ## 4. Update User 132 | 133 | **Url** : `http://127.0.0.1:8080/api/user/common/queryAll` 134 | 135 | **Method** : `POST` 136 | 137 | **Params** : `name`, `age`, `address` 138 | 139 | **Auth Required** : `Yes` 140 | 141 | **Request** : 142 | 143 | ```shell script 144 | curl -X POST -H "Content-Type:application/json" \ 145 | http://127.0.0.1:8080/api/user/auth/update -d'{"name":"david", "age":30, "address":"beijing"}' \ 146 | -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Njk1NTc3NjIsImlhdCI6MTU2OTU1NzE2MiwiaWQiOjEsIm5iZiI6MTU2OTU1NzE2MiwidXNlcm5hbWUiOiJKYW1lcyJ9.-WTzVOqRe6-Ax3M4TpGIACjEqef8PqzcKArLF4_gmRE" 147 | ``` 148 | 149 | **Response** : 150 | ``` 151 | { 152 | "code":0, 153 | "msg":"ok" 154 | } 155 | ``` -------------------------------------------------------------------------------- /admin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SERVER="vc-gin-api" 4 | BASE_DIR=$PWD 5 | INTERVAL=2 6 | 7 | ARGS="-c conf/config.yaml" 8 | 9 | function start() 10 | { 11 | if [ "`pgrep $SERVER -u $UID`" != "" ];then 12 | echo "$SERVER already running" 13 | exit 1 14 | fi 15 | 16 | nohup $BASE_DIR/$SERVER $ARGS server &>/dev/null & 17 | 18 | echo "sleeping..." && sleep $INTERVAL 19 | 20 | if [ "`pgrep $SERVER -u $UID`" == "" ];then 21 | echo "$SERVER start failed" 22 | exit 1 23 | fi 24 | } 25 | 26 | function status() 27 | { 28 | if [ "`pgrep $SERVER -u $UID`" != "" ];then 29 | echo $SERVER is running 30 | else 31 | echo $SERVER is not running 32 | fi 33 | } 34 | 35 | function stop() 36 | { 37 | if [ "`pgrep $SERVER -u $UID`" != "" ];then 38 | kill -9 `pgrep $SERVER -u $UID` 39 | fi 40 | 41 | echo "sleeping..." && sleep $INTERVAL 42 | 43 | if [ "`pgrep $SERVER -u $UID`" != "" ];then 44 | echo "$SERVER stop failed" 45 | exit 1 46 | fi 47 | } 48 | 49 | case "$1" in 50 | 'start') 51 | start 52 | ;; 53 | 'stop') 54 | stop 55 | ;; 56 | 'status') 57 | status 58 | ;; 59 | 'restart') 60 | stop && start 61 | ;; 62 | *) 63 | echo "usage: $0 {start|stop|restart|status}" 64 | exit 1 65 | ;; 66 | esac -------------------------------------------------------------------------------- /conf/config.yaml: -------------------------------------------------------------------------------- 1 | run_mode: debug 2 | addr: :8080 3 | name: vc-gin-api 4 | url: http://127.0.0.1:8080 5 | max_ping_count: 10 6 | jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 7 | 8 | logger: 9 | writers: file,stdout 10 | logger_level: INFO 11 | logger_file: logs/vc-gin-api.log 12 | log_format_text: false 13 | rollingPolicy: size 14 | log_rotate_date: 1 15 | log_rotate_size: 50 16 | log_backup_count: 7 17 | 18 | db: 19 | schema: demo 20 | host: 127.0.0.1 21 | port: 3306 22 | username: root 23 | password: root123456 24 | 25 | redis: 26 | host: 127.0.0.1:6379 27 | index: 0 28 | pool_size: 10 29 | password: 123456 30 | -------------------------------------------------------------------------------- /dao/account_dao.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | "vc-gin-api/model" 7 | "vc-gin-api/pkg" 8 | "vc-gin-api/pkg/log" 9 | ) 10 | 11 | type AccountDao struct { 12 | db *sqlx.DB 13 | } 14 | 15 | func NewAccountDao() *AccountDao { 16 | return &AccountDao{pkg.DB} 17 | } 18 | 19 | func (dao *AccountDao) GetAccountByName(name string) (*model.Account, error) { 20 | sql := fmt.Sprintf(`select id, name, salt, pwd_crypt, status, is_admin `) 21 | sql += fmt.Sprintf(`from account `) 22 | sql += fmt.Sprintf(`where name=? `) 23 | log.Infof("GetAccountByName sql:%v", sql) 24 | 25 | rows, err := dao.db.Queryx(sql, name) 26 | if err != nil { 27 | log.Errorf(err, "GetAccountByName error:%v", sql) 28 | return nil, err 29 | } 30 | defer rows.Close() 31 | item := &model.Account{} 32 | for rows.Next() { 33 | rows.StructScan(item) 34 | } 35 | 36 | return item, nil 37 | } 38 | 39 | func (dao *AccountDao) UpdateAccountPwd(name, salt, pwdCrypt string) error { 40 | sql := fmt.Sprintf(`update account set salt=?, pwd_crypt=? where name=? `) 41 | if _, err := dao.db.Exec(sql, salt, pwdCrypt, name); err != nil { 42 | log.Errorf(err, "UpdateAccountPwd error") 43 | return err 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /dao/user_dao.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | "vc-gin-api/model" 7 | "vc-gin-api/pkg" 8 | "vc-gin-api/pkg/log" 9 | ) 10 | 11 | type UserDao struct { 12 | db *sqlx.DB 13 | } 14 | 15 | func NewUserDao() *UserDao { 16 | return &UserDao{pkg.DB} 17 | } 18 | 19 | func (dao *UserDao) GetUsers(req *model.UserReq) ([]*model.User, int64, error) { 20 | totalSql := fmt.Sprintf(`select count(1) as total from user `) 21 | querySql := fmt.Sprintf(`select id, name, age, address from user `) 22 | 23 | filterSql := fmt.Sprintf(`where 1=1 `) 24 | if req.Name != "" { 25 | filterSql += fmt.Sprintf(`and name='%v' `, req.Name) 26 | } 27 | if req.Address != "" { 28 | filterSql += fmt.Sprintf(`and address like '%v%v%v' `, "%", req.Address, "%") 29 | } 30 | 31 | sortSql := fmt.Sprintf(`order by id asc `) 32 | limitSql := fmt.Sprintf(`limit %v, %v `, req.Start, req.Limit) 33 | 34 | totalSql += filterSql 35 | querySql += filterSql + sortSql + limitSql 36 | 37 | log.Infof("GetUsers totalSql:%v", totalSql) 38 | row1, err := dao.db.Queryx(totalSql) 39 | if err != nil { 40 | log.Errorf(err, "GetUsers error:%v", totalSql) 41 | return nil, 0, err 42 | } 43 | defer row1.Close() 44 | total := int64(0) 45 | if row1.Next() { 46 | row1.Scan(&total) 47 | } 48 | 49 | log.Infof("GetUsers querySql:%v", querySql) 50 | row2, err := dao.db.Queryx(querySql) 51 | if err != nil { 52 | log.Errorf(err, "GetUsers error:%v", querySql) 53 | return nil, 0, err 54 | } 55 | defer row2.Close() 56 | list := make([]*model.User, 0) 57 | for row2.Next() { 58 | item := &model.User{} 59 | row2.StructScan(item) 60 | list = append(list, item) 61 | } 62 | 63 | return list, total, nil 64 | } 65 | 66 | func (dao *UserDao) AddUser(form *model.UserForm) error { 67 | sql := fmt.Sprintf(`insert into user(name, age, address) `) 68 | sql += fmt.Sprintf(`values(?, ?, ?) `) 69 | 70 | log.Infof("AddUser sql:%v", sql) 71 | if _, err := dao.db.Exec(sql, form.Name, form.Age, form.Address); err != nil { 72 | log.Errorf(err, "AddUser error:%v", sql) 73 | return err 74 | } 75 | return nil 76 | } 77 | 78 | func (dao *UserDao) UpdateUser(form *model.UserForm) error { 79 | sql := fmt.Sprintf(`update user set name=?, age=?, address=? `) 80 | sql += fmt.Sprintf(`where name=? `) 81 | 82 | log.Infof("UpdateUser sql:%v", sql) 83 | if _, err := dao.db.Exec(sql, form.Name, form.Age, form.Address, form.Name); err != nil { 84 | log.Errorf(err, "UpdateUser error:%v", sql) 85 | } 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /extra/schema/demo.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 3 | 4 | USE `demo`; 5 | 6 | DROP TABLE IF EXISTS `account`; 7 | CREATE TABLE `account` ( 8 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 9 | `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户', 10 | `salt` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'salt', 11 | `pwd_crypt` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '密码加密', 12 | `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态 0:无效 1:有效', 13 | `is_admin` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'is_admin 0:否 1:是', 14 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 15 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', 16 | PRIMARY KEY (`id`), 17 | UNIQUE KEY `idx_name` (`name`) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='账号表'; 19 | 20 | DROP TABLE IF EXISTS `user`; 21 | CREATE TABLE `user` ( 22 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 23 | `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', 24 | `age` int(4) unsigned NOT NULL DEFAULT '0' COMMENT '年龄', 25 | `address` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '地址', 26 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 27 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', 28 | PRIMARY KEY (`id`) 29 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'; -------------------------------------------------------------------------------- /handler/account_handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "vc-gin-api/model" 6 | "vc-gin-api/pkg/errno" 7 | "vc-gin-api/pkg/log" 8 | "vc-gin-api/service" 9 | "vc-gin-api/util" 10 | ) 11 | 12 | func LoginAccount(c *gin.Context) { 13 | form := &model.LoginForm{} 14 | if err := c.ShouldBindJSON(form); err != nil { 15 | log.Errorf(err, "c.ShouldBindJSON error") 16 | SendResponse(c, errno.ErrParam, nil) 17 | return 18 | } 19 | 20 | service := service.NewAccountService() 21 | 22 | resp, err := service.LoginAccount(form) 23 | if err != nil { 24 | SendResponse(c, err, nil) 25 | return 26 | } 27 | 28 | SendResponse(c, nil, resp) 29 | } 30 | 31 | func UpdateAccountPwd(c *gin.Context) { 32 | model := &model.UpdatePwdForm{} 33 | if err := c.ShouldBindJSON(model); err != nil { 34 | log.Errorf(err, "c.ShouldBindJSON error") 35 | SendResponse(c, errno.ErrParam, nil) 36 | return 37 | } 38 | if util.IsHan(model.NewPwd) { 39 | SendResponse(c, errno.ErrPwdHasHan, nil) 40 | return 41 | } 42 | if len(model.NewPwd) < 8 { 43 | SendResponse(c, errno.ErrNewPwdLen, nil) 44 | return 45 | } 46 | 47 | service := service.NewAccountService() 48 | 49 | if err := service.UpdateAccountPwd(model); err != nil { 50 | SendResponse(c, err, nil) 51 | return 52 | } 53 | 54 | SendResponse(c, nil, nil) 55 | } 56 | -------------------------------------------------------------------------------- /handler/base_handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "vc-gin-api/pkg/errno" 7 | ) 8 | 9 | type Response struct { 10 | Code int `json:"code"` 11 | Msg string `json:"msg"` 12 | Data interface{} `json:"data,omitempty"` 13 | } 14 | 15 | func SendResponse(c *gin.Context, err error, data interface{}) { 16 | code, msg := errno.DecodeErr(err) 17 | c.JSON(http.StatusOK, Response{ 18 | Code: code, 19 | Msg: msg, 20 | Data: data, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /handler/check_handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/shirou/gopsutil/cpu" 7 | "github.com/shirou/gopsutil/disk" 8 | "github.com/shirou/gopsutil/load" 9 | "github.com/shirou/gopsutil/mem" 10 | "net/http" 11 | ) 12 | 13 | const ( 14 | B = 1 15 | KB = 1024 * B 16 | MB = 1024 * KB 17 | GB = 1024 * MB 18 | ) 19 | 20 | func HealthCheck(c *gin.Context) { 21 | message := "OK" 22 | c.String(http.StatusOK, "\n"+message) 23 | } 24 | 25 | func DiskCheck(c *gin.Context) { 26 | u, _ := disk.Usage("/") 27 | 28 | usedMB := int(u.Used) / MB 29 | usedGB := int(u.Used) / GB 30 | totalMB := int(u.Total) / MB 31 | totalGB := int(u.Total) / GB 32 | usedPercent := int(u.UsedPercent) 33 | 34 | status := http.StatusOK 35 | text := "OK" 36 | 37 | if usedPercent >= 95 { 38 | status = http.StatusInternalServerError 39 | text = "CRITICAL" 40 | } else if usedPercent >= 90 { 41 | status = http.StatusTooManyRequests 42 | text = "WARNING" 43 | } 44 | 45 | message := fmt.Sprintf("%s - Free Space: %dMB (%dGB) / %dMB (%dGB) | Used:%d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) 46 | c.String(status, "\n"+message) 47 | } 48 | 49 | func CPUCheck(c *gin.Context) { 50 | cores, _ := cpu.Counts(false) 51 | 52 | a, _ := load.Avg() 53 | l1 := a.Load1 54 | l5 := a.Load5 55 | l15 := a.Load15 56 | 57 | status := http.StatusOK 58 | text := "OK" 59 | 60 | if l5 >= float64(cores-1) { 61 | status = http.StatusInternalServerError 62 | text = "CRITICAL" 63 | } else if l5 >= float64(cores-2) { 64 | status = http.StatusTooManyRequests 65 | text = "WARNING" 66 | } 67 | 68 | message := fmt.Sprintf("%s - Load Average: %.2f, %.2f, %.2f |Cores: %d", text, l1, l5, l15, cores) 69 | c.String(status, "\n"+message) 70 | } 71 | 72 | func RAMCheck(c *gin.Context) { 73 | u, _ := mem.VirtualMemory() 74 | usedMB := int(u.Used) / MB 75 | usedGB := int(u.Used) / GB 76 | totalMB := int(u.Total) / MB 77 | totalGB := int(u.Total) / GB 78 | usedPercent := int(u.UsedPercent) 79 | 80 | status := http.StatusOK 81 | text := "OK" 82 | 83 | if usedPercent >= 95 { 84 | status = http.StatusInternalServerError 85 | text = "CRITICAL" 86 | } else if usedPercent >= 90 { 87 | status = http.StatusTooManyRequests 88 | text = "WARNING" 89 | } 90 | 91 | message := fmt.Sprintf("%s - Free Space: %dMB (%dGB) / %dMB (%dGB) | Used:%d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) 92 | c.String(status, "\n"+message) 93 | } 94 | -------------------------------------------------------------------------------- /handler/user_handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "vc-gin-api/model" 6 | "vc-gin-api/pkg/errno" 7 | "vc-gin-api/pkg/log" 8 | "vc-gin-api/service" 9 | ) 10 | 11 | func QueryUsers(c *gin.Context) { 12 | req := &model.UserReq{} 13 | if err := c.ShouldBind(req); err != nil { 14 | log.Errorf(err, "c.ShouldBind error") 15 | SendResponse(c, errno.ErrParam, nil) 16 | return 17 | } 18 | if req.Start < 0 { 19 | req.Start = 0 20 | } 21 | if req.Limit > 50 { 22 | req.Limit = 50 23 | } 24 | 25 | service := service.NewUserService() 26 | 27 | resp, err := service.QueryUsers(req) 28 | if err != nil { 29 | SendResponse(c, err, nil) 30 | return 31 | } 32 | 33 | SendResponse(c, nil, resp) 34 | } 35 | 36 | func AddUser(c *gin.Context) { 37 | form := &model.UserForm{} 38 | if err := c.ShouldBindJSON(form); err != nil { 39 | SendResponse(c, err, nil) 40 | return 41 | } 42 | 43 | service := service.NewUserService() 44 | 45 | if err := service.AddUser(form); err != nil { 46 | SendResponse(c, err, nil) 47 | return 48 | } 49 | 50 | SendResponse(c, nil, nil) 51 | } 52 | 53 | func UpdateUser(c *gin.Context) { 54 | form := &model.UserForm{} 55 | if err := c.ShouldBindJSON(form); err != nil { 56 | SendResponse(c, err, nil) 57 | return 58 | } 59 | 60 | service := service.NewUserService() 61 | 62 | if err := service.UpdateUser(form); err != nil { 63 | SendResponse(c, err, nil) 64 | return 65 | } 66 | 67 | SendResponse(c, nil, nil) 68 | } 69 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "github.com/gin-gonic/gin" 7 | "github.com/spf13/pflag" 8 | "github.com/spf13/viper" 9 | "net/http" 10 | "time" 11 | "vc-gin-api/pkg" 12 | "vc-gin-api/pkg/log" 13 | "vc-gin-api/router" 14 | "vc-gin-api/router/middleware" 15 | ) 16 | 17 | var ( 18 | cfg = pflag.StringP("config", "c", "", "config file path") 19 | ) 20 | 21 | func main() { 22 | pflag.Parse() 23 | 24 | pkg.Init(*cfg) 25 | 26 | gin.SetMode(viper.GetString("run_mode")) 27 | 28 | g := gin.New() 29 | 30 | router.Load( 31 | g, 32 | middleware.Logging(), 33 | ) 34 | 35 | go func() { 36 | if err := pingServer(); err != nil { 37 | log.Fatal("the router has no response, or it might took too long to start up", err) 38 | } 39 | log.Info("the router has been deployed successfully") 40 | }() 41 | 42 | log.Infof("start to listening the incoming requests on http address: %s", viper.GetString("addr")) 43 | log.Infof("ListenAndServe:%v", http.ListenAndServe(viper.GetString("addr"), g).Error()) 44 | 45 | } 46 | 47 | func pingServer() error { 48 | for i := 0; i < viper.GetInt("max_ping_count"); i++ { 49 | tr := &http.Transport{ 50 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 51 | } 52 | client := &http.Client{Transport: tr} 53 | resp, err := client.Get(viper.GetString("url") + "/api/check/health") 54 | 55 | if err == nil && resp.StatusCode == 200 { 56 | return nil 57 | } 58 | log.Info("Waiting for the router, retry in 1 second") 59 | time.Sleep(time.Second) 60 | } 61 | return errors.New("connect to the router error") 62 | } 63 | -------------------------------------------------------------------------------- /model/account_model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Account struct { 4 | ID uint64 `db:"id" json:"id"` 5 | Name string `db:"name" json:"name"` 6 | Salt string `db:"salt" json:"salt"` 7 | PwdCrypt string `db:"pwd_crypt" json:"pwdCrypt"` 8 | Status int `db:"status" json:"status"` 9 | IsAdmin int `db:"is_admin" json:"isAdmin"` 10 | } 11 | 12 | type LoginForm struct { 13 | Name string `form:"name" binding:"required"` 14 | Password string `form:"password" binding:"required"` 15 | } 16 | 17 | type AccountResp struct { 18 | Name string `json:"name"` 19 | Token string `json:"token"` 20 | } 21 | 22 | type UpdatePwdForm struct { 23 | Name string `form:"name" binding:"required"` 24 | OldPwd string `form:"oldPwd" binding:"required"` 25 | NewPwd string `form:"newPwd" binding:"required"` 26 | } 27 | -------------------------------------------------------------------------------- /model/base_model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Page struct { 4 | Start int64 `form:"start,default=0"` 5 | Limit int64 `form:"limit,default=20"` 6 | } 7 | -------------------------------------------------------------------------------- /model/user_model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type User struct { 4 | ID int64 `db:"id" json:"id"` 5 | Name string `db:"name" json:"name"` 6 | Age int `db:"age" json:"age"` 7 | Address string `db:"address" json:"address"` 8 | } 9 | 10 | type UserReq struct { 11 | Page 12 | Name string `form:"name"` 13 | Address string `form:"address"` 14 | } 15 | 16 | type UserResp struct { 17 | Total int64 `json:"total"` 18 | Rows []*User `json:"rows"` 19 | } 20 | 21 | type UserForm struct { 22 | Name string `form:"name" binding:"required"` 23 | Age int64 `form:"age" binding:"required"` 24 | Address string `form:"address" binding:"required"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/api_token.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/dgrijalva/jwt-go" 7 | "github.com/gin-gonic/gin" 8 | "github.com/spf13/viper" 9 | "time" 10 | ) 11 | 12 | var ( 13 | ErrMissingHeader = errors.New("the length of the authorization header is zero") 14 | ) 15 | 16 | type Context struct { 17 | ID uint64 18 | Username string 19 | } 20 | 21 | func ParseRequest(c *gin.Context) (*Context, error) { 22 | header := c.Request.Header.Get("Authorization") 23 | if len(header) == 0 { 24 | return &Context{}, ErrMissingHeader 25 | } 26 | 27 | secret := viper.GetString("jwt_secret") 28 | 29 | var t string 30 | fmt.Sscanf(header, "Bearer %s", &t) 31 | return Parse(t, secret) 32 | } 33 | 34 | func Parse(tokenString string, secret string) (*Context, error) { 35 | ctx := &Context{} 36 | 37 | token, err := jwt.Parse(tokenString, secretFunc(secret)) 38 | if err != nil { 39 | return ctx, err 40 | } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 41 | ctx.ID = uint64(claims["id"].(float64)) 42 | ctx.Username = claims["username"].(string) 43 | return ctx, nil 44 | } else { 45 | return ctx, err 46 | } 47 | } 48 | 49 | func Sign(c Context) (tokenString string, err error) { 50 | secret := viper.GetString("jwt_secret") 51 | 52 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 53 | "id": c.ID, 54 | "username": c.Username, 55 | "exp": time.Now().Unix() + 60*30, 56 | "nbf": time.Now().Unix(), 57 | "iat": time.Now().Unix(), 58 | }) 59 | tokenString, err = token.SignedString([]byte(secret)) 60 | return 61 | } 62 | 63 | func secretFunc(secret string) jwt.Keyfunc { 64 | return func(token *jwt.Token) (interface{}, error) { 65 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 66 | return nil, jwt.ErrSignatureInvalid 67 | } 68 | 69 | return []byte(secret), nil 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/config.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/fsnotify/fsnotify" 5 | "github.com/jmoiron/sqlx" 6 | "github.com/spf13/viper" 7 | "gopkg.in/redis.v5" 8 | "vc-gin-api/pkg/log" 9 | ) 10 | 11 | var ( 12 | Cfg *Config 13 | DB *sqlx.DB 14 | RedisCli *redis.Client 15 | ) 16 | 17 | type Config struct { 18 | Logger *LoggerConfig 19 | DB *DBConfig 20 | Redis *RedisConfig 21 | } 22 | 23 | func Init(cfgName string) { 24 | setConfig(cfgName) 25 | Cfg = loadConfig() 26 | initConfig(Cfg) 27 | watchConfig() 28 | } 29 | 30 | func setConfig(cfgName string) { 31 | if cfgName != "" { 32 | viper.SetConfigFile(cfgName) 33 | } else { 34 | viper.AddConfigPath("conf") 35 | viper.SetConfigName("config") 36 | } 37 | 38 | if err := viper.ReadInConfig(); err != nil { 39 | panic("viper.ReadInConfig error") 40 | } 41 | } 42 | 43 | func loadConfig() *Config { 44 | cfg := &Config{ 45 | Logger: LoadLoggerConfig(viper.Sub("logger")), 46 | DB: LoadDbConfig(viper.Sub("db")), 47 | Redis: LoadRedisConfig(viper.Sub("redis")), 48 | } 49 | 50 | return cfg 51 | } 52 | 53 | func initConfig(cfg *Config) { 54 | cfg.Logger.InitLogger() 55 | DB = cfg.DB.InitDB() 56 | //RedisCli = cfg.Redis.InitRedis() 57 | } 58 | 59 | func watchConfig() { 60 | viper.WatchConfig() 61 | viper.OnConfigChange(func(e fsnotify.Event) { 62 | log.Infof("config file changed:%s", e.Name) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/errno/code.go: -------------------------------------------------------------------------------- 1 | package errno 2 | 3 | var ( 4 | OK = &Errno{Code: 0, Msg: "ok"} 5 | InternalServerError = &Errno{Code: 1, Msg: "internal server error"} 6 | ErrParam = &Errno{Code: 2, Msg: "request param error"} 7 | ErrTokenInvalid = &Errno{Code: 3, Msg: "the token was invalid"} 8 | ErrAccount = &Errno{Code: 4, Msg: "account or password error"} 9 | ErrPwdHasHan = &Errno{Code: 5, Msg: "pwd has han"} 10 | ErrNewPwdLen = &Errno{Code: 6, Msg: "the len of new pass should >= 8"} 11 | ErrDataExist = &Errno{Code: 7, Msg: "data existed"} 12 | ErrDataNotExist = &Errno{Code: 8, Msg: "data not exist"} 13 | ) 14 | -------------------------------------------------------------------------------- /pkg/errno/errno.go: -------------------------------------------------------------------------------- 1 | package errno 2 | 3 | import "fmt" 4 | 5 | type Errno struct { 6 | Code int 7 | Msg string 8 | } 9 | 10 | func (err Errno) Error() string { 11 | return err.Msg 12 | } 13 | 14 | type Err struct { 15 | Code int 16 | Msg string 17 | Err error 18 | } 19 | 20 | func (err *Err) Error() string { 21 | return fmt.Sprintf("Err - code: %d, msg: %s, error: %s", err.Code, err.Msg, err.Err) 22 | } 23 | 24 | func DecodeErr(err error) (int, string) { 25 | if err == nil { 26 | return OK.Code, OK.Msg 27 | } 28 | 29 | switch typed := err.(type) { 30 | case *Err: 31 | return typed.Code, typed.Msg 32 | case *Errno: 33 | return typed.Code, typed.Msg 34 | default: 35 | } 36 | return InternalServerError.Code, err.Error() 37 | } 38 | -------------------------------------------------------------------------------- /pkg/log/README.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | 该日志包参考 [paas-lager](https://github.com/ServiceComb/paas-lager),做了一些便捷性上的改动,功能完全一样,只不过该日志包更便捷些。 4 | 5 | Go开发中常用的log包有: 6 | 7 | + log 8 | + glog 9 | + logrus 10 | + ... 11 | 12 | `log`和`glog`比较简单,无法满足生产级的程序开发。`logrus`功能很强大,但缺少rotate功能,需要自己通过外部的程序来rotate日志文件。该日志包总结了企业开发中常用的需求,将这些功能整合在一个日志包中。经过测试该日志包性能完全可以满足企业级的生产需求。 13 | 14 | ## 使用方法 15 | 16 | 在使用log包前,需要先初始化log包,初始化函数有:`InitWithConfig()`, `InitWithFile()`。一个简单的example: 17 | 18 | ``` 19 | func main() { 20 | log.InitWithFile("log.yaml") 21 | 22 | for i := 0; i < 1; i++ { 23 | log.Infof("Hi %s, system is starting up ...", "paas-bot") 24 | log.Info("check-info", lager.Data{ 25 | "info": "something", 26 | }) 27 | 28 | log.Debug("check-info", lager.Data{ 29 | "info": "something", 30 | }) 31 | 32 | log.Warn("failed-to-do-somthing", lager.Data{ 33 | "info": "something", 34 | }) 35 | 36 | err := fmt.Errorf("This is an error") 37 | log.Error("failed-to-do-somthing", err) 38 | 39 | log.Info("shutting-down") 40 | } 41 | } 42 | ``` 43 | 44 | log.yaml文件为: 45 | 46 | ``` 47 | writers: file,stdout 48 | logger_level: DEBUG 49 | logger_file: logs/log.log 50 | log_format_text: false 51 | rollingPolicy: size # size, daily 52 | log_rotate_date: 1 53 | log_rotate_size: 1 54 | log_backup_count: 7 55 | ``` 56 | 57 | ## 日志参数 58 | 59 | + `writers`: 输出位置,有2个可选项:file,stdout。选择file会将日志记录到`logger_file`指定的日志文件中,选择stdout会将日志输出到标准输出,当然也可以两者同时选择 60 | + `logger_level`: 日志级别,DEBUG, INFO, WARN, ERROR, FATAL 61 | + `logger_file`: 日志文件 62 | + `log_format_text`: 日志的输出格式,json或者plaintext,`true`会输出成json格式,`false`会输出成非json格式 63 | + `rollingPolicy`: rotate依据,可选的有:daily, size。如果选daily则根据天进行转存,如果是size则根据大小进行转存 64 | + `log_rotate_date`: rotate转存时间,配合`rollingPolicy: daily`使用 65 | + `log_rotate_size`: rotate转存大小,配合`rollingPolicy: size`使用 66 | + `log_backup_count`:当日志文件达到转存标准时,log系统会将该日志文件进行压缩备份,这里指定了备份文件的最大个数。 67 | -------------------------------------------------------------------------------- /pkg/log/example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "vc-gin-api/pkg/log" 6 | "vc-gin-api/pkg/log/lager" 7 | ) 8 | 9 | func main() { 10 | log.InitWithFile("log.yaml") 11 | 12 | for i := 0; i < 1; i++ { 13 | log.Infof("Hi %s, system is starting up ...", "paas-bot") 14 | log.Info("check-info", lager.Data{ 15 | "info": "something", 16 | }) 17 | 18 | log.Debug("check-info", lager.Data{ 19 | "info": "something", 20 | }) 21 | 22 | log.Warn("failed-to-do-somthing", lager.Data{ 23 | "info": "something", 24 | }) 25 | 26 | err := fmt.Errorf("this is an error") 27 | log.Error("failed-to-do-somthing", err) 28 | 29 | log.Info("shutting-down") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg/log/example/log.yaml: -------------------------------------------------------------------------------- 1 | writers: file,stdout 2 | logger_level: DEBUG 3 | logger_file: logs/log.log 4 | log_format_text: false 5 | rollingPolicy: size # size, daily 6 | log_rotate_date: 1 7 | log_rotate_size: 1 8 | log_backup_count: 7 9 | -------------------------------------------------------------------------------- /pkg/log/lager.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "vc-gin-api/pkg/log/lager" 11 | 12 | "gopkg.in/yaml.v2" 13 | ) 14 | 15 | // constant values for logrotate parameters 16 | const ( 17 | RollingPolicySize = "size" 18 | LogRotateDate = 1 19 | LogRotateSize = 10 20 | LogBackupCount = 7 21 | ) 22 | 23 | // Lager struct for logger parameters 24 | type Lager struct { 25 | Writers string `yaml:"writers"` 26 | LoggerLevel string `yaml:"logger_level"` 27 | LoggerFile string `yaml:"logger_file"` 28 | LogFormatText bool `yaml:"log_format_text"` 29 | RollingPolicy string `yaml:"rollingPolicy"` 30 | LogRotateDate int `yaml:"log_rotate_date"` 31 | LogRotateSize int `yaml:"log_rotate_size"` 32 | LogBackupCount int `yaml:"log_backup_count"` 33 | } 34 | 35 | //PassLagerCfg is the struct for lager information(passlager.yaml) 36 | type PassLagerCfg struct { 37 | Writers string `yaml:"writers"` 38 | LoggerLevel string `yaml:"logger_level"` 39 | LoggerFile string `yaml:"logger_file"` 40 | LogFormatText bool `yaml:"log_format_text"` 41 | RollingPolicy string `yaml:"rollingPolicy"` 42 | LogRotateDate int `yaml:"log_rotate_date"` 43 | LogRotateSize int `yaml:"log_rotate_size"` 44 | LogBackupCount int `yaml:"log_backup_count"` 45 | } 46 | 47 | // Logger is the global variable for the object of lager.Logger 48 | var Logger lager.Logger 49 | 50 | // logFilePath log file path 51 | var logFilePath string 52 | 53 | // PassLagerDefinition is having the information about loging 54 | var PassLagerDefinition *PassLagerCfg = DefaultLagerDefinition() 55 | 56 | // Initialize Build constructs a *Lager.Logger with the configured parameters. 57 | func Initialize(writers, loggerLevel, loggerFile, rollingPolicy string, logFormatText bool, 58 | LogRotateDate, LogRotateSize, LogBackupCount int) { 59 | lag := &Lager{ 60 | Writers: writers, 61 | LoggerLevel: loggerLevel, 62 | LoggerFile: loggerFile, 63 | LogFormatText: logFormatText, 64 | RollingPolicy: rollingPolicy, 65 | LogRotateDate: LogRotateDate, 66 | LogRotateSize: LogRotateSize, 67 | LogBackupCount: LogBackupCount, 68 | } 69 | 70 | Logger = newLog(lag) 71 | initLogRotate(logFilePath, lag) 72 | return 73 | } 74 | 75 | // newLog new log 76 | func newLog(lag *Lager) lager.Logger { 77 | checkPassLagerDefinition(lag) 78 | 79 | if filepath.IsAbs(lag.LoggerFile) { 80 | createLogFile("", lag.LoggerFile) 81 | logFilePath = filepath.Join("", lag.LoggerFile) 82 | } else { 83 | createLogFile(os.Getenv("CHASSIS_HOME"), lag.LoggerFile) 84 | logFilePath = filepath.Join(os.Getenv("CHASSIS_HOME"), lag.LoggerFile) 85 | } 86 | writers := strings.Split(strings.TrimSpace(lag.Writers), ",") 87 | if len(strings.TrimSpace(lag.Writers)) == 0 { 88 | writers = []string{"stdout"} 89 | } 90 | LagerInit(Config{ 91 | Writers: writers, 92 | LoggerLevel: lag.LoggerLevel, 93 | LoggerFile: logFilePath, 94 | LogFormatText: lag.LogFormatText, 95 | }) 96 | 97 | logger := NewLogger(lag.LoggerFile) 98 | return logger 99 | } 100 | 101 | // checkPassLagerDefinition check pass lager definition 102 | func checkPassLagerDefinition(lag *Lager) { 103 | if lag.LoggerLevel == "" { 104 | lag.LoggerLevel = "DEBUG" 105 | } 106 | 107 | if lag.LoggerFile == "" { 108 | lag.LoggerFile = "log/chassis.log" 109 | } 110 | 111 | if lag.RollingPolicy == "" { 112 | log.Println("RollingPolicy is empty, use default policy[size]") 113 | lag.RollingPolicy = RollingPolicySize 114 | } else if lag.RollingPolicy != "daily" && lag.RollingPolicy != RollingPolicySize { 115 | log.Printf("RollingPolicy is error, RollingPolicy=%s, use default policy[size].", lag.RollingPolicy) 116 | lag.RollingPolicy = RollingPolicySize 117 | } 118 | 119 | if lag.LogRotateDate <= 0 || lag.LogRotateDate > 10 { 120 | lag.LogRotateDate = LogRotateDate 121 | } 122 | 123 | if lag.LogRotateSize <= 0 || lag.LogRotateSize > 50 { 124 | lag.LogRotateSize = LogRotateSize 125 | } 126 | 127 | if lag.LogBackupCount < 0 || lag.LogBackupCount > 100 { 128 | lag.LogBackupCount = LogBackupCount 129 | } 130 | } 131 | 132 | // createLogFile create log file 133 | func createLogFile(localPath, outputpath string) { 134 | _, err := os.Stat(strings.Replace(filepath.Dir(filepath.Join(localPath, outputpath)), "\\", "/", -1)) 135 | if err != nil && os.IsNotExist(err) { 136 | os.MkdirAll(strings.Replace(filepath.Dir(filepath.Join(localPath, outputpath)), "\\", "/", -1), os.ModePerm) 137 | } else if err != nil { 138 | panic(err) 139 | } 140 | f, err := os.OpenFile(strings.Replace(filepath.Join(localPath, outputpath), "\\", "/", -1), os.O_CREATE, os.ModePerm) 141 | if err != nil { 142 | panic(err) 143 | } 144 | defer f.Close() 145 | } 146 | 147 | // readPassLagerConfigFile is unmarshal the paas lager configuration file(lager.yaml) 148 | func InitWithFile(lagerFile string) error { 149 | if lagerFile == "" { 150 | log.Printf("log config file is empty, use default config: `%s`\n", marshalDefinition()) 151 | return Init() 152 | } 153 | 154 | passLagerDef := PassLagerCfg{} 155 | yamlFile, err := ioutil.ReadFile(lagerFile) 156 | if err != nil { 157 | log.Printf("yamlFile.Get err #%v, use default config: `%s`\n", err, marshalDefinition()) 158 | return Init() 159 | } 160 | 161 | err = yaml.Unmarshal(yamlFile, &passLagerDef) 162 | if err != nil { 163 | log.Printf("Unmarshal: %v, use default config: `%s`\n", err, marshalDefinition()) 164 | return Init() 165 | } 166 | 167 | PassLagerDefinition = &passLagerDef 168 | return Init() 169 | } 170 | 171 | func InitWithConfig(passLagerDef *PassLagerCfg) error { 172 | PassLagerDefinition = passLagerDef 173 | return Init() 174 | } 175 | 176 | func DefaultLagerDefinition() *PassLagerCfg { 177 | cfg := PassLagerCfg{ 178 | Writers: "stdout,file", 179 | LoggerLevel: "DEBUG", 180 | LoggerFile: "logs/chassis.log", 181 | LogFormatText: false, 182 | RollingPolicy: RollingPolicySize, 183 | LogRotateDate: 1, 184 | LogRotateSize: 10, 185 | LogBackupCount: 7, 186 | } 187 | 188 | return &cfg 189 | } 190 | 191 | func Init() error { 192 | Initialize(PassLagerDefinition.Writers, PassLagerDefinition.LoggerLevel, 193 | PassLagerDefinition.LoggerFile, PassLagerDefinition.RollingPolicy, 194 | PassLagerDefinition.LogFormatText, PassLagerDefinition.LogRotateDate, 195 | PassLagerDefinition.LogRotateSize, PassLagerDefinition.LogBackupCount) 196 | 197 | return nil 198 | } 199 | 200 | func marshalDefinition() string { 201 | data, _ := json.Marshal(PassLagerDefinition) 202 | return string(data) 203 | } 204 | -------------------------------------------------------------------------------- /pkg/log/lager/color.go: -------------------------------------------------------------------------------- 1 | package lager 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | //Black is a constant of type int 9 | Black = iota + 30 10 | Red 11 | Green 12 | Yellow 13 | Blue 14 | Magenta 15 | Cyan 16 | White 17 | ) 18 | 19 | var ( 20 | //InfoByte is a variable of type []byte 21 | DebugByte = []byte(fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", Blue, "DEBUG")) 22 | WarnByte = []byte(fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", Yellow, "WARN")) 23 | ErrorByte = []byte(fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", Red, "ERROR")) 24 | FatalByte = []byte(fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", Magenta, "FATAL")) 25 | ) 26 | -------------------------------------------------------------------------------- /pkg/log/lager/logger.go: -------------------------------------------------------------------------------- 1 | package lager 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strconv" 7 | "strings" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | //StackTraceBufferSize is a constant which defines stack track buffer size 13 | const StackTraceBufferSize = 1024 * 100 14 | 15 | //Logger is a interface 16 | type Logger interface { 17 | RegisterSink(Sink) 18 | Session(task string, data ...Data) Logger 19 | SessionName() string 20 | Debug(action string, data ...Data) 21 | Info(action string, data ...Data) 22 | Warn(action string, data ...Data) 23 | Error(action string, err error, data ...Data) 24 | Fatal(action string, err error, data ...Data) 25 | Debugf(format string, args ...interface{}) 26 | Infof(format string, args ...interface{}) 27 | Warnf(format string, args ...interface{}) 28 | Errorf(err error, format string, args ...interface{}) 29 | Fatalf(err error, format string, args ...interface{}) 30 | WithData(Data) Logger 31 | } 32 | 33 | type logger struct { 34 | component string 35 | task string 36 | sinks []Sink 37 | sessionID string 38 | nextSession uint64 39 | data Data 40 | logFormatText bool 41 | } 42 | 43 | //NewLoggerExt is a function which returns logger struct object 44 | func NewLoggerExt(component string, isFormatText bool) Logger { 45 | return &logger{ 46 | component: component, 47 | task: component, 48 | sinks: []Sink{}, 49 | data: Data{}, 50 | logFormatText: isFormatText, 51 | } 52 | } 53 | 54 | //NewLogger is a function used to get new logger object 55 | func NewLogger(component string) Logger { 56 | return NewLoggerExt(component, true) 57 | } 58 | 59 | //RegisterSink is a function used to register sink 60 | func (l *logger) RegisterSink(sink Sink) { 61 | l.sinks = append(l.sinks, sink) 62 | } 63 | 64 | //SessionName is used to get the session name 65 | func (l *logger) SessionName() string { 66 | return l.task 67 | } 68 | 69 | //Session is a function which returns logger details for that session 70 | func (l *logger) Session(task string, data ...Data) Logger { 71 | sid := atomic.AddUint64(&l.nextSession, 1) 72 | 73 | var sessionIDstr string 74 | 75 | if l.sessionID != "" { 76 | sessionIDstr = fmt.Sprintf("%s.%d", l.sessionID, sid) 77 | } else { 78 | sessionIDstr = fmt.Sprintf("%d", sid) 79 | } 80 | 81 | return &logger{ 82 | component: l.component, 83 | task: fmt.Sprintf("%s.%s", l.task, task), 84 | sinks: l.sinks, 85 | sessionID: sessionIDstr, 86 | data: l.baseData(data...), 87 | } 88 | } 89 | 90 | //WithData which adds data to the logger object 91 | func (l *logger) WithData(data Data) Logger { 92 | return &logger{ 93 | component: l.component, 94 | task: l.task, 95 | sinks: l.sinks, 96 | sessionID: l.sessionID, 97 | data: l.baseData(data), 98 | } 99 | } 100 | 101 | // Find the sink need to log 102 | func (l *logger) activeSinks(loglevel LogLevel) []Sink { 103 | ss := make([]Sink, len(l.sinks)) 104 | idx := 0 105 | for _, itf := range l.sinks { 106 | if s, ok := itf.(*writerSink); ok && loglevel < s.minLogLevel { 107 | continue 108 | } 109 | if s, ok := itf.(*ReconfigurableSink); ok && loglevel < LogLevel(s.minLogLevel) { 110 | continue 111 | } 112 | ss[idx] = itf 113 | idx++ 114 | } 115 | return ss[:idx] 116 | } 117 | 118 | func (l *logger) log(loglevel LogLevel, action string, err error, data ...Data) { 119 | ss := l.activeSinks(loglevel) 120 | if len(ss) == 0 { 121 | return 122 | } 123 | l.logs(ss, loglevel, action, err, data...) 124 | } 125 | 126 | func (l *logger) logs(ss []Sink, loglevel LogLevel, action string, err error, data ...Data) { 127 | logData := l.baseData(data...) 128 | 129 | if err != nil { 130 | logData["error"] = err.Error() 131 | } 132 | 133 | if loglevel == FATAL { 134 | stackTrace := make([]byte, StackTraceBufferSize) 135 | stackSize := runtime.Stack(stackTrace, false) 136 | stackTrace = stackTrace[:stackSize] 137 | 138 | logData["trace"] = string(stackTrace) 139 | } 140 | 141 | log := LogFormat{ 142 | Timestamp: currentTimestamp(), 143 | Message: action, 144 | LogLevel: loglevel, 145 | Data: logData, 146 | } 147 | 148 | // add file, lineno 149 | addExtLogInfo(&log) 150 | var logInfo string 151 | for _, sink := range l.sinks { 152 | if l.logFormatText { 153 | levelstr := FormatLogLevel(log.LogLevel) 154 | extraData, ok := log.Data["error"].(string) 155 | if ok && extraData != "" { 156 | extraData = " error: " + extraData 157 | } 158 | logInfo = log.Timestamp + " " + levelstr + " " + log.File + " " + log.Message + extraData 159 | sink.Log(loglevel, []byte(logInfo)) 160 | 161 | } else { 162 | logInfo, jserr := log.ToJSON() 163 | if jserr != nil { 164 | fmt.Printf("[lager] ToJSON() ERROR! action: %s, jserr: %s, log: %+v", action, jserr, log) 165 | // also output json marshal error event to sink 166 | log.Data = Data{"Data": fmt.Sprint(logData)} 167 | jsonerrdata, _ := log.ToJSON() 168 | sink.Log(ERROR, jsonerrdata) 169 | continue 170 | } 171 | sink.Log(loglevel, logInfo) 172 | } 173 | } 174 | 175 | if loglevel == FATAL { 176 | panic(err) 177 | } 178 | } 179 | 180 | func (l *logger) Debug(action string, data ...Data) { 181 | l.log(DEBUG, action, nil, data...) 182 | } 183 | 184 | func (l *logger) Info(action string, data ...Data) { 185 | l.log(INFO, action, nil, data...) 186 | } 187 | 188 | func (l *logger) Warn(action string, data ...Data) { 189 | l.log(WARN, action, nil, data...) 190 | } 191 | 192 | func (l *logger) Error(action string, err error, data ...Data) { 193 | l.log(ERROR, action, err, data...) 194 | } 195 | 196 | func (l *logger) Fatal(action string, err error, data ...Data) { 197 | l.log(FATAL, action, err, data...) 198 | } 199 | 200 | func (l *logger) logf(loglevel LogLevel, err error, format string, args ...interface{}) { 201 | ss := l.activeSinks(loglevel) 202 | if len(ss) == 0 { 203 | return 204 | } 205 | logmsg := fmt.Sprintf(format, args...) 206 | l.logs(ss, loglevel, logmsg, err) 207 | } 208 | 209 | func (l *logger) Debugf(format string, args ...interface{}) { 210 | l.logf(DEBUG, nil, format, args...) 211 | } 212 | 213 | func (l *logger) Infof(format string, args ...interface{}) { 214 | l.logf(INFO, nil, format, args...) 215 | } 216 | 217 | func (l *logger) Warnf(format string, args ...interface{}) { 218 | l.logf(WARN, nil, format, args...) 219 | } 220 | 221 | func (l *logger) Errorf(err error, format string, args ...interface{}) { 222 | l.logf(ERROR, err, format, args...) 223 | } 224 | 225 | func (l *logger) Fatalf(err error, format string, args ...interface{}) { 226 | l.logf(FATAL, err, format, args...) 227 | } 228 | 229 | func (l *logger) baseData(givenData ...Data) Data { 230 | data := Data{} 231 | 232 | for k, v := range l.data { 233 | data[k] = v 234 | } 235 | 236 | if len(givenData) > 0 { 237 | for _, dataArg := range givenData { 238 | for key, val := range dataArg { 239 | data[key] = val 240 | } 241 | } 242 | } 243 | 244 | if l.sessionID != "" { 245 | data["session"] = l.sessionID 246 | } 247 | 248 | return data 249 | } 250 | 251 | func currentTimestamp() string { 252 | //return time.Now().Format("2006-01-02 15:04:05.000 -07:00") 253 | return time.Now().Format("2006-01-02 15:04:05.000") 254 | } 255 | 256 | func addExtLogInfo(logf *LogFormat) { 257 | 258 | for i := 4; i <= 5; i++ { 259 | _, file, line, ok := runtime.Caller(i) 260 | 261 | if strings.Index(file, "st_lager.go") > 0 { 262 | continue 263 | } 264 | 265 | if ok { 266 | idx := strings.LastIndex(file, "src") 267 | switch { 268 | case idx >= 0: 269 | logf.File = file[idx+4:] 270 | default: 271 | logf.File = file 272 | } 273 | // depth: 2 274 | indexFunc := func(file string) string { 275 | backup := "/" + file 276 | lastSlashIndex := strings.LastIndex(backup, "/") 277 | if lastSlashIndex < 0 { 278 | return backup 279 | } 280 | secondLastSlashIndex := strings.LastIndex(backup[:lastSlashIndex], "/") 281 | if secondLastSlashIndex < 0 { 282 | return backup[lastSlashIndex+1:] 283 | } 284 | return backup[secondLastSlashIndex+1:] 285 | } 286 | logf.File = indexFunc(logf.File) + ":" + strconv.Itoa(line) 287 | } 288 | break 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /pkg/log/lager/models.go: -------------------------------------------------------------------------------- 1 | package lager 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | //LogLevel is a user defined variable of type int 8 | type LogLevel int 9 | 10 | const ( 11 | //DEBUG is a constant of user defined type LogLevel 12 | DEBUG LogLevel = iota 13 | INFO 14 | WARN 15 | ERROR 16 | FATAL 17 | ) 18 | 19 | //FormatLogLevel is a function which returns string format of log level 20 | func FormatLogLevel(x LogLevel) string { 21 | var level string 22 | switch x { 23 | case DEBUG: 24 | level = "DEBUG" 25 | case INFO: 26 | level = "INFO" 27 | case WARN: 28 | level = "WARN" 29 | case ERROR: 30 | level = "ERROR" 31 | case FATAL: 32 | level = "FATAL" 33 | } 34 | return level 35 | } 36 | 37 | //MarshalJSON is a function which returns data in JSON format 38 | func (x LogLevel) MarshalJSON() ([]byte, error) { 39 | // var level string 40 | var level = FormatLogLevel(x) 41 | return json.Marshal(level) 42 | } 43 | 44 | //Data is a map 45 | type Data map[string]interface{} 46 | 47 | //LogFormat is a struct which stores details about log 48 | type LogFormat struct { 49 | LogLevel LogLevel `json:"level"` 50 | Timestamp string `json:"timestamp"` 51 | File string `json:"file"` 52 | Message string `json:"msg"` 53 | Data Data `json:"data,omitempty"` 54 | } 55 | 56 | //ToJSON which converts data of log file in to JSON file 57 | func (log LogFormat) ToJSON() ([]byte, error) { 58 | return json.Marshal(log) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/log/lager/reconfigurable_sink.go: -------------------------------------------------------------------------------- 1 | package lager 2 | 3 | import "sync/atomic" 4 | 5 | //ReconfigurableSink is a struct 6 | type ReconfigurableSink struct { 7 | sink Sink 8 | 9 | minLogLevel int32 10 | } 11 | 12 | //NewReconfigurableSink is a function which returns struct object 13 | func NewReconfigurableSink(sink Sink, initialMinLogLevel LogLevel) *ReconfigurableSink { 14 | return &ReconfigurableSink{ 15 | sink: sink, 16 | 17 | minLogLevel: int32(initialMinLogLevel), 18 | } 19 | } 20 | 21 | //Log is a method which returns log level and log 22 | func (sink *ReconfigurableSink) Log(level LogLevel, log []byte) { 23 | minLogLevel := LogLevel(atomic.LoadInt32(&sink.minLogLevel)) 24 | 25 | if level < minLogLevel { 26 | return 27 | } 28 | 29 | sink.sink.Log(level, log) 30 | } 31 | 32 | //SetMinLevel is a function which sets minimum log level 33 | func (sink *ReconfigurableSink) SetMinLevel(level LogLevel) { 34 | atomic.StoreInt32(&sink.minLogLevel, int32(level)) 35 | } 36 | 37 | //GetMinLevel is a method which gets minimum log level 38 | func (sink *ReconfigurableSink) GetMinLevel() LogLevel { 39 | return LogLevel(atomic.LoadInt32(&sink.minLogLevel)) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/log/lager/writer_sink.go: -------------------------------------------------------------------------------- 1 | package lager 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "sync" 7 | ) 8 | 9 | const logBufferSize = 1024 10 | 11 | // A Sink represents a write destination for a Logger. It provides 12 | // a thread-safe interface for writing logs 13 | type Sink interface { 14 | //Log to the sink. Best effort -- no need to worry about errors. 15 | Log(level LogLevel, payload []byte) 16 | } 17 | 18 | type writerSink struct { 19 | writer io.Writer 20 | minLogLevel LogLevel 21 | name string 22 | writeL *sync.Mutex 23 | } 24 | 25 | //NewWriterSink is function which returns new struct object 26 | func NewWriterSink(name string, writer io.Writer, minLogLevel LogLevel) Sink { 27 | return &writerSink{ 28 | writer: writer, 29 | minLogLevel: minLogLevel, 30 | writeL: new(sync.Mutex), 31 | name: name, 32 | } 33 | } 34 | 35 | func (sink *writerSink) Log(level LogLevel, log []byte) { 36 | if level < sink.minLogLevel { 37 | return 38 | } 39 | if sink.name == "stdout" { 40 | if bytes.Contains(log, []byte("WARN")) { 41 | log = bytes.Replace(log, []byte("WARN"), WarnByte, -1) 42 | } else if bytes.Contains(log, []byte("ERROR")) { 43 | log = bytes.Replace(log, []byte("ERROR"), ErrorByte, -1) 44 | } else if bytes.Contains(log, []byte("FATAL")) { 45 | log = bytes.Replace(log, []byte("FATAL"), FatalByte, -1) 46 | } else if bytes.Contains(log, []byte("DEBUG")) { 47 | log = bytes.Replace(log, []byte("DEBUG"), DebugByte, -1) 48 | } 49 | } 50 | log = append(log, '\n') 51 | sink.writeL.Lock() 52 | sink.writer.Write(log) 53 | sink.writeL.Unlock() 54 | } 55 | -------------------------------------------------------------------------------- /pkg/log/log.yaml: -------------------------------------------------------------------------------- 1 | writers: file,stdout 2 | logger_level: DEBUG 3 | logger_file: log/chassis.log 4 | log_format_text: true 5 | rollingPolicy: size # size, daily 6 | log_rotate_date: 1 7 | log_rotate_size: 1 8 | log_backup_count: 7 9 | -------------------------------------------------------------------------------- /pkg/log/logrotate.go: -------------------------------------------------------------------------------- 1 | //Copyright 2017 Huawei Technologies Co., Ltd 2 | // 3 | //Licensed under the Apache License, Version 2.0 (the "License"); 4 | //you may not use this file except in compliance with the License. 5 | //You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | //Unless required by applicable law or agreed to in writing, software 10 | //distributed under the License is distributed on an "AS IS" BASIS, 11 | //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | // Package lager is the package for lager 16 | package log 17 | 18 | import ( 19 | "archive/zip" 20 | "fmt" 21 | "io" 22 | "log" 23 | "os" 24 | "path/filepath" 25 | "regexp" 26 | "sort" 27 | "strings" 28 | "time" 29 | ) 30 | 31 | var pathReplacer *strings.Replacer 32 | 33 | // EscapPath escape path 34 | func EscapPath(msg string) string { 35 | return pathReplacer.Replace(msg) 36 | } 37 | 38 | func removeFile(path string) error { 39 | fileInfo, err := os.Stat(path) 40 | if err != nil { 41 | return err 42 | } 43 | if fileInfo.IsDir() { 44 | return nil 45 | } 46 | err = os.Remove(path) 47 | if err != nil { 48 | return err 49 | } 50 | return nil 51 | } 52 | 53 | func removeExceededFiles(path string, baseFileName string, 54 | maxKeptCount int, rotateStage string) { 55 | if maxKeptCount < 0 { 56 | return 57 | } 58 | fileList := make([]string, 0, 2*maxKeptCount) 59 | var pat string 60 | if rotateStage == "rollover" { 61 | //rotated file, svc.log.20060102150405000 62 | pat = fmt.Sprintf(`%s\.[0-9]{1,17}$`, baseFileName) 63 | } else if rotateStage == "backup" { 64 | //backup compressed file, svc.log.20060102150405000.zip 65 | pat = fmt.Sprintf(`%s\.[0-9]{17}\.zip$`, baseFileName) 66 | } else { 67 | return 68 | } 69 | fileList, err := FilterFileList(path, pat) 70 | if err != nil { 71 | Logger.Errorf(err, "filepath.Walk() path: %s failed.", EscapPath(path)) 72 | return 73 | } 74 | sort.Strings(fileList) 75 | if len(fileList) <= maxKeptCount { 76 | return 77 | } 78 | //remove exceeded files, keep file count below maxBackupCount 79 | for len(fileList) > maxKeptCount { 80 | filePath := fileList[0] 81 | err := removeFile(filePath) 82 | if err != nil { 83 | Logger.Errorf(err, "remove filePath: %s failed.", EscapPath(filePath)) 84 | break 85 | } 86 | //remove the first element of a list 87 | fileList = append(fileList[:0], fileList[1:]...) 88 | } 89 | } 90 | 91 | //filePath: file full path, like ${_APP_LOG_DIR}/svc.log.1 92 | //fileBaseName: rollover file base name, like svc.log 93 | //replaceTimestamp: whether or not to replace the num. of a rolled file 94 | func compressFile(filePath, fileBaseName string, replaceTimestamp bool) error { 95 | ifp, err := os.Open(filePath) 96 | if err != nil { 97 | return err 98 | } 99 | defer ifp.Close() 100 | 101 | var zipFilePath string 102 | if replaceTimestamp { 103 | //svc.log.1 -> svc.log.20060102150405000.zip 104 | zipFileBase := fileBaseName + "." + getTimeStamp() + "." + "zip" 105 | zipFilePath = filepath.Dir(filePath) + "/" + zipFileBase 106 | } else { 107 | zipFilePath = filePath + ".zip" 108 | } 109 | zipFile, err := os.OpenFile(zipFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0440) 110 | if err != nil { 111 | return err 112 | } 113 | defer zipFile.Close() 114 | 115 | zipWriter := zip.NewWriter(zipFile) 116 | defer zipWriter.Close() 117 | 118 | ofp, err := zipWriter.Create(filepath.Base(filePath)) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | _, err = io.Copy(ofp, ifp) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | return nil 129 | } 130 | 131 | func shouldRollover(fPath string, MaxFileSize int) bool { 132 | if MaxFileSize < 0 { 133 | return false 134 | } 135 | 136 | fileInfo, err := os.Stat(fPath) 137 | if err != nil { 138 | Logger.Errorf(err, "state path: %s failed.", EscapPath(fPath)) 139 | return false 140 | } 141 | 142 | if fileInfo.Size() > int64(MaxFileSize*1024*1024) { 143 | return true 144 | } 145 | return false 146 | } 147 | 148 | func doRollover(fPath string, MaxFileSize int, MaxBackupCount int) { 149 | if !shouldRollover(fPath, MaxFileSize) { 150 | return 151 | } 152 | 153 | timeStamp := getTimeStamp() 154 | //absolute path 155 | rotateFile := fPath + "." + timeStamp 156 | err := CopyFile(fPath, rotateFile) 157 | if err != nil { 158 | Logger.Errorf(err, "copy path: %s failed.", EscapPath(fPath)) 159 | } 160 | 161 | //truncate the file 162 | f, err := os.OpenFile(fPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) 163 | if err != nil { 164 | Logger.Errorf(err, "truncate path: %s failed.", EscapPath(fPath)) 165 | return 166 | } 167 | f.Close() 168 | 169 | //remove exceeded rotate files 170 | removeExceededFiles(filepath.Dir(fPath), filepath.Base(fPath), MaxBackupCount, "rollover") 171 | } 172 | 173 | func doBackup(fPath string, MaxBackupCount int) { 174 | if MaxBackupCount <= 0 { 175 | return 176 | } 177 | pat := fmt.Sprintf(`%s\.[0-9]{1,17}$`, filepath.Base(fPath)) 178 | rotateFileList, err := FilterFileList(filepath.Dir(fPath), pat) 179 | if err != nil { 180 | Logger.Errorf(err, "walk path: %s failed.", EscapPath(fPath)) 181 | return 182 | } 183 | 184 | for _, file := range rotateFileList { 185 | var err error 186 | p := fmt.Sprintf(`%s\.[0-9]{17}$`, filepath.Base(fPath)) 187 | if ret, _ := regexp.MatchString(p, file); ret { 188 | //svc.log.20060102150405000, not replace Timestamp 189 | err = compressFile(file, filepath.Base(fPath), false) 190 | } else { 191 | //svc.log.1, replace Timestamp 192 | err = compressFile(file, filepath.Base(fPath), true) 193 | } 194 | if err != nil { 195 | Logger.Errorf(err, "compress path: %s failed.", EscapPath(file)) 196 | continue 197 | } 198 | err = removeFile(file) 199 | if err != nil { 200 | Logger.Errorf(err, "remove path %s failed.", EscapPath(file)) 201 | } 202 | } 203 | 204 | //remove exceeded backup files 205 | removeExceededFiles(filepath.Dir(fPath), filepath.Base(fPath), MaxBackupCount, "backup") 206 | } 207 | 208 | func logRotateFile(file string, MaxFileSize int, MaxBackupCount int) { 209 | defer func() { 210 | if e := recover(); e != nil { 211 | Logger.Errorf(nil, "LogRotate file path: %s catch an exception.", EscapPath(file)) 212 | } 213 | }() 214 | 215 | doRollover(file, MaxFileSize, MaxBackupCount) 216 | doBackup(file, MaxBackupCount) 217 | } 218 | 219 | // LogRotate function for log rotate 220 | // path: where log files need rollover 221 | // MaxFileSize: MaxSize of a file before rotate. By M Bytes. 222 | // MaxBackupCount: Max counts to keep of a log's backup files. 223 | func LogRotate(path string, MaxFileSize int, MaxBackupCount int) { 224 | //filter .log .trace files 225 | defer func() { 226 | if e := recover(); e != nil { 227 | Logger.Errorf(nil, "LogRotate catch an exception") 228 | } 229 | }() 230 | 231 | pat := `.(\.log|\.trace|\.out)$` 232 | fileList, err := FilterFileList(path, pat) 233 | if err != nil { 234 | Logger.Errorf(err, "filepath.Walk() path: %s failed.", path) 235 | return 236 | } 237 | 238 | for _, file := range fileList { 239 | logRotateFile(file, MaxFileSize, MaxBackupCount) 240 | } 241 | } 242 | 243 | // FilterFileList function for filter file list 244 | //path : where the file will be filtered 245 | //pat : regexp pattern to filter the matched file 246 | func FilterFileList(path, pat string) ([]string, error) { 247 | capacity := 10 248 | //initialize a fileName slice, len=0, cap=10 249 | fileList := make([]string, 0, capacity) 250 | err := filepath.Walk(path, 251 | func(pathName string, f os.FileInfo, e error) error { 252 | if f == nil { 253 | return e 254 | } 255 | if f.IsDir() { 256 | return nil 257 | } 258 | if pat != "" { 259 | ret, _ := regexp.MatchString(pat, f.Name()) 260 | if !ret { 261 | return nil 262 | } 263 | } 264 | fileList = append(fileList, pathName) 265 | return nil 266 | }) 267 | return fileList, err 268 | } 269 | 270 | // getTimeStamp get time stamp 271 | func getTimeStamp() string { 272 | now := time.Now().Format("2006.01.02.15.04.05.000") 273 | timeSlot := strings.Replace(now, ".", "", -1) 274 | return timeSlot 275 | } 276 | 277 | // CopyFile copy file 278 | func CopyFile(srcFile, destFile string) error { 279 | file, err := os.Open(srcFile) 280 | if err != nil { 281 | return err 282 | } 283 | defer file.Close() 284 | dest, err := os.Create(destFile) 285 | if err != nil { 286 | return err 287 | } 288 | defer dest.Close() 289 | _, err = io.Copy(dest, file) 290 | return err 291 | } 292 | 293 | // initLogRotate initialize log rotate 294 | func initLogRotate(logFilePath string, c *Lager) { 295 | if logFilePath == "" { 296 | return 297 | } 298 | checkConfig(c) 299 | if c == nil { 300 | go func() { 301 | for { 302 | LogRotate(filepath.Dir(logFilePath), LogRotateSize, LogBackupCount) 303 | time.Sleep(30 * time.Second) 304 | } 305 | }() 306 | } else { 307 | if c.RollingPolicy == RollingPolicySize { 308 | go func() { 309 | for { 310 | LogRotate(filepath.Dir(logFilePath), c.LogRotateSize, c.LogBackupCount) 311 | time.Sleep(30 * time.Second) 312 | } 313 | }() 314 | } else { 315 | go func() { 316 | for { 317 | LogRotate(filepath.Dir(logFilePath), 0, c.LogBackupCount) 318 | time.Sleep(24 * time.Hour * time.Duration(c.LogRotateDate)) 319 | } 320 | }() 321 | } 322 | } 323 | } 324 | 325 | // checkPassLagerDefinition check pass lager definition 326 | func checkConfig(c *Lager) { 327 | if c.RollingPolicy == "" { 328 | log.Println("RollingPolicy is empty, use default policy[size]") 329 | c.RollingPolicy = RollingPolicySize 330 | } else if c.RollingPolicy != "daily" && c.RollingPolicy != RollingPolicySize { 331 | log.Printf("RollingPolicy is error, RollingPolicy=%s, use default policy[size].", c.RollingPolicy) 332 | c.RollingPolicy = RollingPolicySize 333 | } 334 | 335 | if c.LogRotateDate <= 0 || c.LogRotateDate > 10 { 336 | c.LogRotateDate = LogRotateDate 337 | } 338 | 339 | if c.LogRotateSize <= 0 || c.LogRotateSize > 50 { 340 | c.LogRotateSize = LogRotateSize 341 | } 342 | 343 | if c.LogBackupCount < 0 || c.LogBackupCount > 100 { 344 | c.LogBackupCount = LogBackupCount 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /pkg/log/st_lager.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "strings" 9 | "sync" 10 | "vc-gin-api/pkg/log/lager" 11 | ) 12 | 13 | const ( 14 | //DEBUG is a constant of string type 15 | DEBUG = "DEBUG" 16 | INFO = "INFO" 17 | WARN = "WARN" 18 | ERROR = "ERROR" 19 | FATAL = "FATAL" 20 | ) 21 | 22 | //Config is a struct which stores details for maintaining logs 23 | type Config struct { 24 | LoggerLevel string 25 | LoggerFile string 26 | Writers []string 27 | EnableRsyslog bool 28 | RsyslogNetwork string 29 | RsyslogAddr string 30 | 31 | LogFormatText bool 32 | } 33 | 34 | var config = DefaultConfig() 35 | var m sync.RWMutex 36 | 37 | //Writers is a map 38 | var Writers = make(map[string]io.Writer) 39 | 40 | //RegisterWriter is used to register a io writer 41 | func RegisterWriter(name string, writer io.Writer) { 42 | m.Lock() 43 | Writers[name] = writer 44 | m.Unlock() 45 | } 46 | 47 | //DefaultConfig is a function which retuns config object with default configuration 48 | func DefaultConfig() *Config { 49 | return &Config{ 50 | LoggerLevel: INFO, 51 | LoggerFile: "", 52 | EnableRsyslog: false, 53 | RsyslogNetwork: "udp", 54 | RsyslogAddr: "127.0.0.1:5140", 55 | LogFormatText: false, 56 | } 57 | } 58 | 59 | //Init is a function which initializes all config struct variables 60 | func LagerInit(c Config) { 61 | if c.LoggerLevel != "" { 62 | config.LoggerLevel = c.LoggerLevel 63 | } 64 | 65 | if c.LoggerFile != "" { 66 | config.LoggerFile = c.LoggerFile 67 | config.Writers = append(config.Writers, "file") 68 | } 69 | 70 | if c.EnableRsyslog { 71 | config.EnableRsyslog = c.EnableRsyslog 72 | } 73 | 74 | if c.RsyslogNetwork != "" { 75 | config.RsyslogNetwork = c.RsyslogNetwork 76 | } 77 | 78 | if c.RsyslogAddr != "" { 79 | config.RsyslogAddr = c.RsyslogAddr 80 | } 81 | if len(c.Writers) == 0 { 82 | config.Writers = append(config.Writers, "stdout") 83 | 84 | } else { 85 | config.Writers = c.Writers 86 | } 87 | config.LogFormatText = c.LogFormatText 88 | RegisterWriter("stdout", os.Stdout) 89 | var file io.Writer 90 | var err error 91 | if config.LoggerFile != "" { 92 | file, err = os.OpenFile(config.LoggerFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) 93 | if err != nil { 94 | panic(err) 95 | } 96 | 97 | } 98 | for _, sink := range config.Writers { 99 | if sink == "file" { 100 | if file == nil { 101 | log.Panic("Must set file path") 102 | } 103 | RegisterWriter("file", file) 104 | } 105 | } 106 | } 107 | 108 | //NewLogger is a function 109 | func NewLogger(component string) lager.Logger { 110 | return NewLoggerExt(component, component) 111 | } 112 | 113 | //NewLoggerExt is a function which is used to write new logs 114 | func NewLoggerExt(component string, appGUID string) lager.Logger { 115 | var lagerLogLevel lager.LogLevel 116 | switch strings.ToUpper(config.LoggerLevel) { 117 | case DEBUG: 118 | lagerLogLevel = lager.DEBUG 119 | case INFO: 120 | lagerLogLevel = lager.INFO 121 | case WARN: 122 | lagerLogLevel = lager.WARN 123 | case ERROR: 124 | lagerLogLevel = lager.ERROR 125 | case FATAL: 126 | lagerLogLevel = lager.FATAL 127 | default: 128 | panic(fmt.Errorf("unknown logger level: %s", config.LoggerLevel)) 129 | } 130 | logger := lager.NewLoggerExt(component, config.LogFormatText) 131 | for _, sink := range config.Writers { 132 | 133 | writer, ok := Writers[sink] 134 | if !ok { 135 | log.Panic("Unknow writer: ", sink) 136 | } 137 | sink := lager.NewReconfigurableSink(lager.NewWriterSink(sink, writer, lager.DEBUG), lagerLogLevel) 138 | logger.RegisterSink(sink) 139 | } 140 | 141 | return logger 142 | } 143 | 144 | func Debug(action string, data ...lager.Data) { 145 | Logger.Debug(action, data...) 146 | } 147 | 148 | func Info(action string, data ...lager.Data) { 149 | Logger.Info(action, data...) 150 | } 151 | 152 | func Warn(action string, data ...lager.Data) { 153 | Logger.Warn(action, data...) 154 | } 155 | 156 | func Error(action string, err error, data ...lager.Data) { 157 | Logger.Error(action, err, data...) 158 | } 159 | 160 | func Fatal(action string, err error, data ...lager.Data) { 161 | Logger.Fatal(action, err, data...) 162 | } 163 | 164 | func Debugf(format string, args ...interface{}) { 165 | Logger.Debugf(format, args...) 166 | } 167 | 168 | func Infof(format string, args ...interface{}) { 169 | Logger.Infof(format, args...) 170 | } 171 | 172 | func Warnf(format string, args ...interface{}) { 173 | Logger.Warnf(format, args...) 174 | } 175 | 176 | func Errorf(err error, format string, args ...interface{}) { 177 | Logger.Errorf(err, format, args...) 178 | } 179 | 180 | func Fatalf(err error, format string, args ...interface{}) { 181 | Logger.Fatalf(err, format, args...) 182 | } 183 | -------------------------------------------------------------------------------- /pkg/logger.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/viper" 6 | "vc-gin-api/pkg/log" 7 | ) 8 | 9 | type LoggerConfig struct { 10 | Writers string 11 | LoggerLevel string 12 | LoggerFile string 13 | LogFormatText bool 14 | RollingPolicy string 15 | LogRotateDate int 16 | LogRotateSize int 17 | LogBackupCount int 18 | } 19 | 20 | func LoadLoggerConfig(viper *viper.Viper) *LoggerConfig { 21 | cfg := &LoggerConfig{ 22 | Writers: viper.GetString("writers"), 23 | LoggerLevel: viper.GetString("logger_level"), 24 | LoggerFile: viper.GetString("logger_file"), 25 | LogFormatText: viper.GetBool("log_format_text"), 26 | RollingPolicy: viper.GetString("rollingPolicy"), 27 | LogRotateDate: viper.GetInt("log_rotate_date"), 28 | LogRotateSize: viper.GetInt("log_rotate_size"), 29 | LogBackupCount: viper.GetInt("log_backup_count"), 30 | } 31 | fmt.Printf("LoadLoggerConfig:%+v\n", cfg) 32 | return cfg 33 | } 34 | 35 | func (cfg *LoggerConfig) InitLogger() { 36 | passLagerCfg := &log.PassLagerCfg{ 37 | Writers: cfg.Writers, 38 | LoggerLevel: cfg.LoggerLevel, 39 | LoggerFile: cfg.LoggerFile, 40 | LogFormatText: cfg.LogFormatText, 41 | RollingPolicy: cfg.RollingPolicy, 42 | LogRotateDate: cfg.LogRotateDate, 43 | LogRotateSize: cfg.LogRotateSize, 44 | LogBackupCount: cfg.LogBackupCount, 45 | } 46 | if err := log.InitWithConfig(passLagerCfg); err != nil { 47 | panic("log.InitWithConfig error") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/mysql.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/jmoiron/sqlx" 7 | "github.com/spf13/viper" 8 | "vc-gin-api/pkg/log" 9 | ) 10 | 11 | type DBConfig struct { 12 | Schema string 13 | Host string 14 | Port string 15 | Username string 16 | Password string 17 | } 18 | 19 | func LoadDbConfig(viper *viper.Viper) *DBConfig { 20 | cfg := &DBConfig{ 21 | Schema: viper.GetString("schema"), 22 | Host: viper.GetString("host"), 23 | Port: viper.GetString("port"), 24 | Username: viper.GetString("username"), 25 | Password: viper.GetString("password"), 26 | } 27 | fmt.Printf("LoadDbConfig:%+v\n", cfg) 28 | return cfg 29 | } 30 | 31 | func (cfg *DBConfig) InitDB() *sqlx.DB { 32 | driverName := "mysql" 33 | dataSourceName := fmt.Sprintf(`%v:%v@tcp(%v:%v)/%v`, cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Schema) 34 | db, err := sqlx.Connect(driverName, dataSourceName) 35 | if err != nil { 36 | log.Errorf(err, "InitDb error:%v", dataSourceName) 37 | panic("InitDb error") 38 | } 39 | return db 40 | } 41 | -------------------------------------------------------------------------------- /pkg/redis.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/spf13/viper" 7 | "gopkg.in/redis.v5" 8 | "time" 9 | "vc-gin-api/pkg/log" 10 | ) 11 | 12 | var ( 13 | MaxErrCnt = 5 14 | ) 15 | 16 | type RedisConfig struct { 17 | Addr string 18 | Password string 19 | DB int 20 | PoolSize int 21 | } 22 | 23 | func LoadRedisConfig(viper *viper.Viper) *RedisConfig { 24 | cfg := &RedisConfig{ 25 | Addr: viper.GetString("host"), 26 | Password: viper.GetString("password"), 27 | DB: viper.GetInt("index"), 28 | PoolSize: viper.GetInt("pool_size"), 29 | } 30 | fmt.Printf("LoadRedisConfig:%+v\n", cfg) 31 | return cfg 32 | } 33 | 34 | func (cfg *RedisConfig) InitRedis() *redis.Client { 35 | redisCli := redis.NewClient(&redis.Options{ 36 | Addr: cfg.Addr, 37 | Password: cfg.Password, 38 | DB: cfg.DB, 39 | PoolSize: cfg.PoolSize, 40 | }) 41 | if redisCli == nil { 42 | panic("redis.NewClient error") 43 | } 44 | _, err := redisCli.Ping().Result() 45 | if err != nil { 46 | panic("redisCli.Ping error") 47 | } 48 | return redisCli 49 | } 50 | 51 | func SetRedisVal(key string, val interface{}, expiration time.Duration) error { 52 | buf, err := json.Marshal(val) 53 | if err != nil { 54 | log.Errorf(err, "json.Marshal error") 55 | return err 56 | } 57 | cmd := RedisCli.Set(key, string(buf), expiration) 58 | if cmd.Err() != nil { 59 | log.Errorf(cmd.Err(), "RedisCli.Set error") 60 | return cmd.Err() 61 | } 62 | return nil 63 | } 64 | 65 | func GetRedisVal(key string) string { 66 | tryCnt := 0 67 | for { 68 | value, err := RedisCli.Get(key).Result() 69 | if err == redis.Nil { 70 | return "" 71 | } else if err != nil { 72 | tryCnt++ 73 | if tryCnt > MaxErrCnt { 74 | log.Errorf(err, "RedisCli.Get error") 75 | return "" 76 | } 77 | } 78 | return value 79 | } 80 | } 81 | 82 | func RedisExists(key string) bool { 83 | errCnt := 0 84 | for { 85 | ret := RedisCli.Exists(key) 86 | if ret == nil || ret.Err() != nil { 87 | errCnt++ 88 | } else { 89 | return ret.Val() 90 | } 91 | if errCnt > MaxErrCnt { 92 | log.Error("RedisCli.Exists error", nil) 93 | break 94 | } 95 | } 96 | return false 97 | } 98 | -------------------------------------------------------------------------------- /pkg/redis_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSetRedisVal(t *testing.T) { 9 | Init("../conf/config.yaml") 10 | key := fmt.Sprintf(`vc-gin-api:test:user:%v`, 1) 11 | user := &User{ 12 | Name: "vince", 13 | Age: 24, 14 | Address: "beijing", 15 | } 16 | if err := SetRedisVal(key, user, 0); err != nil { 17 | t.Errorf("pkg.SetRedisVal error, key:%v, err:%v", key, err) 18 | return 19 | } 20 | t.Log("done success") 21 | } 22 | 23 | type User struct { 24 | Name string `json:"name"` 25 | Age int `json:"age"` 26 | Address string `json:"address"` 27 | } 28 | 29 | func TestGetRedisVal(t *testing.T) { 30 | Init("../conf/config.yaml") 31 | key := fmt.Sprintf(`vc-gin-api:test:user:%v`, 1) 32 | result := GetRedisVal(key) 33 | t.Logf("result:%v\n", result) 34 | } 35 | 36 | func TestRedisExists(t *testing.T) { 37 | Init("../conf/config.yaml") 38 | key := fmt.Sprintf(`vc-gin-api:test:user:%v`, 1) 39 | result := RedisExists(key) 40 | t.Logf("result:%v\n", result) 41 | } 42 | -------------------------------------------------------------------------------- /router/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "vc-gin-api/handler" 6 | "vc-gin-api/pkg" 7 | "vc-gin-api/pkg/errno" 8 | "vc-gin-api/pkg/log" 9 | ) 10 | 11 | func AuthMiddleware() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | if _, err := pkg.ParseRequest(c); err != nil { 14 | log.Errorf(err, "pkg.ParseRequest error") 15 | handler.SendResponse(c, errno.ErrTokenInvalid, nil) 16 | c.Abort() 17 | return 18 | } 19 | c.Next() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /router/middleware/header.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func NoCache(c *gin.Context) { 10 | c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") 11 | c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") 12 | c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) 13 | c.Next() 14 | } 15 | 16 | func Options(c *gin.Context) { 17 | if c.Request.Method != "OPTIONS" { 18 | c.Next() 19 | } else { 20 | c.Header("Access-Control-Allow-Origin", "*") 21 | c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") 22 | c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") 23 | c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") 24 | c.Header("Content-Type", "application/json") 25 | c.AbortWithStatus(200) 26 | } 27 | } 28 | 29 | func Secure(c *gin.Context) { 30 | c.Header("Access-Control-Allow-Origin", "*") 31 | c.Header("X-Frame-Options", "DENY") 32 | c.Header("X-Content-Type-Options", "nosniff") 33 | c.Header("X-XSS-Protection", "1; mode=block") 34 | if c.Request.TLS != nil { 35 | c.Header("Strict-Transport-Security", "max-age=31536000") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /router/middleware/logging.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/gin-gonic/gin" 7 | "github.com/willf/pad" 8 | "io/ioutil" 9 | "time" 10 | "vc-gin-api/handler" 11 | "vc-gin-api/pkg/errno" 12 | "vc-gin-api/pkg/log" 13 | ) 14 | 15 | type bodyLogWriter struct { 16 | gin.ResponseWriter 17 | body *bytes.Buffer 18 | } 19 | 20 | func (w bodyLogWriter) Write(b []byte) (int, error) { 21 | w.body.Write(b) 22 | return w.ResponseWriter.Write(b) 23 | } 24 | 25 | func Logging() gin.HandlerFunc { 26 | return func(c *gin.Context) { 27 | start := time.Now().UTC() 28 | 29 | path := c.Request.URL.Path 30 | if path == "/api/check/health" || path == "/api/check/ram" || path == "/api/check/cpu" || path == "/api/check/disk" { 31 | return 32 | } 33 | 34 | var bodyBytes []byte 35 | if c.Request.Body != nil { 36 | bodyBytes, _ = ioutil.ReadAll(c.Request.Body) 37 | } 38 | 39 | c.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) 40 | 41 | method := c.Request.Method 42 | ip := c.ClientIP() 43 | 44 | blw := &bodyLogWriter{ 45 | body: bytes.NewBufferString(""), 46 | ResponseWriter: c.Writer, 47 | } 48 | c.Writer = blw 49 | c.Next() 50 | 51 | end := time.Now().UTC() 52 | latency := end.Sub(start) 53 | 54 | code, msg := -1, "" 55 | 56 | var response handler.Response 57 | if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { 58 | log.Errorf(err, "json.Unmarshal error, body:`%s`", blw.body.Bytes()) 59 | code = errno.InternalServerError.Code 60 | msg = err.Error() 61 | } else { 62 | code = response.Code 63 | msg = response.Msg 64 | } 65 | 66 | log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, msg) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "vc-gin-api/handler" 7 | "vc-gin-api/router/middleware" 8 | ) 9 | 10 | func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { 11 | g.Use(gin.Recovery()) 12 | g.Use(middleware.NoCache) 13 | g.Use(middleware.Options) 14 | g.Use(middleware.Secure) 15 | g.Use(mw...) 16 | 17 | g.NoRoute(func(c *gin.Context) { 18 | c.String(http.StatusNotFound, "the incorrect api route") 19 | }) 20 | 21 | check := g.Group("/api/check") 22 | { 23 | check.GET("/health", handler.HealthCheck) 24 | check.GET("/disk", handler.DiskCheck) 25 | check.GET("/cpu", handler.CPUCheck) 26 | check.GET("/ram", handler.RAMCheck) 27 | } 28 | 29 | account := g.Group("/api/account") 30 | { 31 | account.POST("/login", handler.LoginAccount) 32 | account.POST("/updatePass", handler.UpdateAccountPwd) 33 | } 34 | 35 | user := g.Group("/api/user/common") 36 | { 37 | user.GET("/queryAll", handler.QueryUsers) 38 | } 39 | 40 | userAuth := g.Group("/api/user/auth") 41 | userAuth.Use(middleware.AuthMiddleware()) 42 | { 43 | userAuth.POST("/add", handler.AddUser) 44 | userAuth.POST("/update", handler.UpdateUser) 45 | } 46 | 47 | return g 48 | } 49 | -------------------------------------------------------------------------------- /service/account_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "vc-gin-api/dao" 5 | "vc-gin-api/model" 6 | "vc-gin-api/pkg" 7 | "vc-gin-api/pkg/errno" 8 | "vc-gin-api/pkg/log" 9 | "vc-gin-api/util" 10 | ) 11 | 12 | type AccountService struct { 13 | accountDao *dao.AccountDao 14 | } 15 | 16 | func NewAccountService() *AccountService { 17 | return &AccountService{dao.NewAccountDao()} 18 | } 19 | 20 | func (service *AccountService) LoginAccount(form *model.LoginForm) (*model.AccountResp, *errno.Errno) { 21 | account, err := service.accountDao.GetAccountByName(form.Name) 22 | if err != nil { 23 | return nil, errno.InternalServerError 24 | } 25 | if account.Name == "" { 26 | return nil, errno.ErrAccount 27 | } 28 | salt := account.Salt 29 | pwdCrypt := util.MD5(salt + form.Password) 30 | if pwdCrypt != account.PwdCrypt { 31 | return nil, errno.ErrAccount 32 | } 33 | 34 | token, err := signApiToken(account.ID, account.Name) 35 | if err != nil { 36 | return nil, errno.InternalServerError 37 | } 38 | resp := &model.AccountResp{ 39 | Name: account.Name, 40 | Token: token, 41 | } 42 | 43 | return resp, nil 44 | } 45 | 46 | func (service *AccountService) UpdateAccountPwd(form *model.UpdatePwdForm) *errno.Errno { 47 | account, err := service.accountDao.GetAccountByName(form.Name) 48 | if err != nil { 49 | return errno.InternalServerError 50 | } 51 | if account.Name == "" { 52 | return errno.ErrAccount 53 | } 54 | oldSalt := account.Salt 55 | oldPwdCrypt := util.MD5(oldSalt + form.OldPwd) 56 | if account.PwdCrypt != oldPwdCrypt { 57 | return errno.ErrAccount 58 | } 59 | newSalt := util.GetRandomString(8) 60 | newPwdCrypt := util.MD5(newSalt + form.NewPwd) 61 | if err := service.accountDao.UpdateAccountPwd(form.Name, newSalt, newPwdCrypt); err != nil { 62 | return errno.InternalServerError 63 | } 64 | return nil 65 | } 66 | 67 | func signApiToken(id uint64, username string) (string, error) { 68 | token, err := pkg.Sign(pkg.Context{ID: id, Username: username}) 69 | if err != nil { 70 | log.Errorf(err, "signApiToken error") 71 | return "", nil 72 | } 73 | 74 | return token, nil 75 | } 76 | -------------------------------------------------------------------------------- /service/user_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "vc-gin-api/dao" 5 | "vc-gin-api/model" 6 | "vc-gin-api/pkg/errno" 7 | ) 8 | 9 | type UserService struct { 10 | userDao *dao.UserDao 11 | } 12 | 13 | func NewUserService() *UserService { 14 | return &UserService{dao.NewUserDao()} 15 | } 16 | 17 | func (service *UserService) QueryUsers(req *model.UserReq) (*model.UserResp, *errno.Errno) { 18 | users, total, err := service.userDao.GetUsers(req) 19 | if err != nil { 20 | return nil, errno.InternalServerError 21 | } 22 | resp := &model.UserResp{ 23 | Rows: users, 24 | Total: total, 25 | } 26 | return resp, nil 27 | } 28 | 29 | func (service *UserService) AddUser(form *model.UserForm) *errno.Errno { 30 | if err := service.userDao.AddUser(form); err != nil { 31 | return errno.InternalServerError 32 | } 33 | return nil 34 | } 35 | 36 | func (service *UserService) UpdateUser(form *model.UserForm) *errno.Errno { 37 | if err := service.userDao.UpdateUser(form); err != nil { 38 | return errno.InternalServerError 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /task/demo_task.go: -------------------------------------------------------------------------------- 1 | package task 2 | -------------------------------------------------------------------------------- /util/common_util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/md5" 8 | "encoding/hex" 9 | "math/rand" 10 | "regexp" 11 | "time" 12 | "unicode" 13 | ) 14 | 15 | func PKCS5Padding(cipherText []byte, blockSize int) []byte { 16 | padding := blockSize - len(cipherText)%blockSize 17 | padText := bytes.Repeat([]byte{byte(padding)}, padding) 18 | return append(cipherText, padText...) 19 | } 20 | 21 | func PKCS5UnPadding(origData []byte) []byte { 22 | length := len(origData) 23 | unPadding := int(origData[length-1]) 24 | return origData[:(length - unPadding)] 25 | } 26 | 27 | func AesEncrypt(origData, key []byte) ([]byte, error) { 28 | block, err := aes.NewCipher(key) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | blockSize := block.BlockSize() 34 | origData = PKCS5Padding(origData, blockSize) 35 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) 36 | crypt := make([]byte, len(origData)) 37 | blockMode.CryptBlocks(crypt, origData) 38 | return crypt, nil 39 | } 40 | 41 | func AesDecrypt(crypt, key []byte) ([]byte, error) { 42 | block, err := aes.NewCipher(key) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | blockSize := block.BlockSize() 48 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) 49 | origData := make([]byte, len(crypt)) 50 | blockMode.CryptBlocks(origData, crypt) 51 | origData = PKCS5UnPadding(origData) 52 | return origData, nil 53 | } 54 | 55 | func MD5(text string) string { 56 | ctx := md5.New() 57 | ctx.Write([]byte(text)) 58 | return hex.EncodeToString(ctx.Sum(nil)) 59 | } 60 | 61 | func IsHan(str string) bool { 62 | for _, r := range str { 63 | if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) { 64 | return true 65 | } 66 | } 67 | return false 68 | } 69 | 70 | func GetRandomString(n int) string { 71 | const symbols = "0123456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNOPQRSTUVWXYZ" 72 | const symbolsIdxBits = 6 73 | const symbolsIdxMask = 1<= 0; { 80 | if remain == 0 { 81 | cache, remain = prng.Int63(), symbolsIdxMax 82 | } 83 | if idx := int(cache & symbolsIdxMask); idx < len(symbols) { 84 | b[i] = symbols[idx] 85 | i-- 86 | } 87 | cache >>= symbolsIdxBits 88 | remain-- 89 | } 90 | return string(b) 91 | } 92 | -------------------------------------------------------------------------------- /util/common_util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestGetRandomString(t *testing.T) { 6 | result := GetRandomString(8) 7 | t.Logf("result:%v", result) 8 | } 9 | 10 | func TestMD5(t *testing.T) { 11 | result := MD5("Yq1QCXOx" + "admin123456") 12 | t.Logf("result:%v", result) 13 | } 14 | -------------------------------------------------------------------------------- /util/convert_util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | func ConvertStringToBool(boolString string, defaultValue bool) bool { 8 | retBool := defaultValue 9 | if len(boolString) > 0 { 10 | b, err := strconv.ParseBool(boolString) 11 | if err == nil { 12 | retBool = b 13 | } 14 | } 15 | return retBool 16 | } 17 | 18 | func ConvertStringToFloat(val string, defaultValue float64) float64 { 19 | ret := defaultValue 20 | if len(val) > 0 { 21 | f, err := strconv.ParseFloat(val, 64) 22 | if err == nil { 23 | ret = f 24 | } 25 | } 26 | return ret 27 | } 28 | 29 | func ConvertStringToInt(intString string, defaultValue int) int { 30 | retInt := defaultValue 31 | if len(intString) > 0 { 32 | i, err := strconv.Atoi(intString) 33 | if err == nil { 34 | retInt = i 35 | } 36 | } 37 | return retInt 38 | } 39 | 40 | func ConvertStringToInt64(intString string, defaultValue int64) int64 { 41 | retInt64 := defaultValue 42 | 43 | if len(intString) > 0 { 44 | i64, err := strconv.ParseInt(intString, 10, 64) 45 | if err == nil { 46 | retInt64 = i64 47 | } 48 | } 49 | return retInt64 50 | } 51 | --------------------------------------------------------------------------------