Bladeren bron

feat: init

double.huang 4 jaren geleden
bovenliggende
commit
d4da763b53

+ 12 - 1
etc/i2bill-api.yaml

@@ -1,8 +1,19 @@
 Name: i2bill-api
 Host: 0.0.0.0
 Port: 8888
+DataSource: root:gSRGZqb121TlYIbJy0@tcp(47.103.202.94:3306)/i2bill?charset=utf8&loc=Local&parseTime=true
+Table:
+Cache:
+  - Host: 47.103.219.158:7001
+    Pass: i2#333
 Transform:
   Etcd:
     Hosts:
       - 47.103.219.158:30019
-    Key: transform.rpc
+    Key: transform.rpc
+Weixin:
+  Appid:
+  Secret:
+JwtAuth:
+  AccessSecret: 6hy789iu87
+  AccessExpire: 604800

+ 4 - 2
go.mod

@@ -4,6 +4,8 @@ go 1.16
 
 require (
 	git.i2edu.net/i2/go-zero v1.0.0
-	git.i2edu.net/i2/i2-bill-erp v0.0.0-20210611100316-9e38be6cc04a
-	github.com/iancoleman/strcase v0.1.3 // indirect
+	git.i2edu.net/i2/i2-bill-erp v0.0.0-20210615090349-f7bddc376275 // indirect
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/satori/go.uuid v1.2.0
+	gopkg.in/yaml.v2 v2.4.0
 )

+ 9 - 13
go.sum

@@ -1,13 +1,14 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-git.i2edu.net/i2/go-zero v0.0.0-20210611055653-34fed21a23cd h1:tGrad9Wk9+IlxsO65FFVugFRqdAEhSz5vnGUetOrt8g=
-git.i2edu.net/i2/go-zero v0.0.0-20210611055653-34fed21a23cd/go.mod h1:a9idDtfMmMXrZIHyDg6XnYjWuCpKG0I6zv6Vo9Fpncc=
 git.i2edu.net/i2/go-zero v1.0.0 h1:tB6YOQ4PwOkZwIrzXV+fv6rZajohZbdsBTLw+PHjo3E=
 git.i2edu.net/i2/go-zero v1.0.0/go.mod h1:a9idDtfMmMXrZIHyDg6XnYjWuCpKG0I6zv6Vo9Fpncc=
-git.i2edu.net/i2/i2-bill-erp v0.0.0-20210611100316-9e38be6cc04a h1:xienQ/CcXV4oJsjDUN/OGGRLgByK3dAg7+nLPz5RNgM=
-git.i2edu.net/i2/i2-bill-erp v0.0.0-20210611100316-9e38be6cc04a/go.mod h1:DunA0mN8Z8g7LbTGZUrDbKSm1VcoAhfra0rDLZBkd2M=
+git.i2edu.net/i2/i2-bill-erp v0.0.0-20210615054657-2a5f8ce2446f h1:hZU9S5PHoZ1u4qJ1qZMI+fK92uozj3ygWOHpnIJ/3tE=
+git.i2edu.net/i2/i2-bill-erp v0.0.0-20210615054657-2a5f8ce2446f/go.mod h1:oWdflHBfbc2hoh4cFsg8HaheBVGHcSTw38BnbGMrk7c=
+git.i2edu.net/i2/i2-bill-erp v0.0.0-20210615090349-f7bddc376275 h1:HsXmEM5HnDNUd44de6CZ1OeDdCt/Z6oQN601WNZ5Pyo=
+git.i2edu.net/i2/i2-bill-erp v0.0.0-20210615090349-f7bddc376275/go.mod h1:oWdflHBfbc2hoh4cFsg8HaheBVGHcSTw38BnbGMrk7c=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
+github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM=
 github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -40,7 +41,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -50,7 +50,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/emicklei/proto v1.9.0 h1:l0QiNT6Qs7Yj0Mb4X6dnWBQer4ebei2BFcgQLbGqUDc=
 github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -58,7 +57,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
-github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
 github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -75,7 +73,9 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
 github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
+github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
 github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -129,7 +129,6 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplb
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
-github.com/iancoleman/strcase v0.1.3 h1:dJBk1m2/qjL1twPLf68JND55vvivMupZ4wIzE8CTdBw=
 github.com/iancoleman/strcase v0.1.3/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
@@ -157,7 +156,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
 github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -215,8 +213,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
@@ -239,16 +238,13 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1
 github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
 github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
-github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
 github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
 go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
 go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=

+ 62 - 4
i2bill.api

@@ -7,6 +7,59 @@ info(
 	email: "2637309949@qq.com"
 )
 
+type WXLoginResponse {
+	OpenID     string `json:"openid"`
+	SessionKey string `json:"session_key"`
+	UnionID    string `json:"unionid"`
+	ErrCode    int    `json:"errcode"`
+	ErrMsg     string `json:"errmsg"`
+}
+
+type Watermark {
+	AppID     string `json:"appid"`
+	TimeStamp int64  `json:"timestamp"`
+}
+
+type WXUserInfo {
+	OpenID    string    `json:"openId,omitempty"`
+	NickName  string    `json:"nickName"`
+	AvatarUrl string    `json:"avatarUrl"`
+	Gender    int64     `json:"gender"`
+	Country   string    `json:"country"`
+	Province  string    `json:"province"`
+	City      string    `json:"city"`
+	UnionID   string    `json:"unionId,omitempty"`
+	Language  string    `json:"language"`
+	Watermark Watermark `json:"watermark,omitempty"`
+}
+
+type ResUserInfo {
+	UserInfo      WXUserInfo `json:"userInfo"`
+	RawData       string     `json:"rawData"`
+	Signature     string     `json:"signature"`
+	EncryptedData string     `json:"encryptedData"`
+	IV            string     `json:"iv"`
+}
+
+type authLoginBody {
+	Code     string      `json:"code"`
+	UserInfo ResUserInfo `json:"userInfo"`
+}
+
+type AuthUserInfo {
+	ID       int64  `json:"id"`
+	UserName string `json:"username"`
+	NickName string `json:"nickname"`
+	Gender   int64  `json:"gender"`
+	Avatar   string `json:"avatar"`
+	Birthday int64  `json:"birthday"`
+}
+
+type authResponse {
+	Token    string       `json:"token"`
+	UserInfo AuthUserInfo `json:"userInfo"`
+}
+
 type request {
 	// TODO: add members here and delete this comment
 	Id int64 `form:"id"`
@@ -17,10 +70,15 @@ type response {
 	Id int64 `json:"id"`
 }
 
+service i2bill-api {
+	@handler LoginByWeixin // TODO
+	post /api/auth/loginByWeixin(authLoginBody) returns(authResponse)
+}
+
+@server(
+	jwt: JwtAuth
+)
 service i2bill-api {
 	@handler GetUser // TODO: set handler name and delete this comment
-	get /users/id/:userId(request) returns(response)
-	
-	@handler CreateUser // TODO: set handler name and delete this comment
-	post /users/create(request)
+	get /api/users/id/:userId(request) returns(response)
 }

+ 0 - 1
i2bill.go

@@ -25,7 +25,6 @@ func main() {
 	defer server.Stop()
 
 	handler.RegisterHandlers(server, ctx)
-
 	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
 	server.Start()
 }

+ 14 - 1
internal/config/config.go

@@ -1,11 +1,24 @@
 package config
 
 import (
+	"git.i2edu.net/i2/go-zero/core/stores/cache"
 	"git.i2edu.net/i2/go-zero/rest"
 	"git.i2edu.net/i2/go-zero/zrpc"
 )
 
+type Weixin struct {
+	Appid  string
+	Secret string
+}
 type Config struct {
 	rest.RestConf
-	Transform zrpc.RpcClientConf
+	DataSource string
+	Table      string
+	Transform  zrpc.RpcClientConf
+	Cache      cache.CacheConf
+	Weixin     Weixin
+	JwtAuth    struct {
+		AccessSecret string
+		AccessExpire int64
+	}
 }

+ 8 - 1
internal/handler/getuserhandler.go

@@ -1,13 +1,16 @@
 package handler
 
 import (
+	"fmt"
 	"net/http"
+	"time"
 
 	"git.i2edu.net/i2/i2-bill-api/internal/logic"
 	"git.i2edu.net/i2/i2-bill-api/internal/svc"
 	"git.i2edu.net/i2/i2-bill-api/internal/types"
 
 	"git.i2edu.net/i2/go-zero/rest/httpx"
+	"git.i2edu.net/i2/go-zero/rest/token"
 )
 
 func GetUserHandler(ctx *svc.ServiceContext) http.HandlerFunc {
@@ -17,7 +20,11 @@ func GetUserHandler(ctx *svc.ServiceContext) http.HandlerFunc {
 			httpx.Error(w, err)
 			return
 		}
-
+		token.NewTokenParser()
+		parser := token.NewTokenParser(token.WithResetDuration(time.Minute))
+		tok, err := parser.ParseToken(r, ctx.Config.JwtAuth.AccessSecret, "")
+		fmt.Println("tok", tok)
+		fmt.Println("err", err)
 		l := logic.NewGetUserLogic(r.Context(), ctx)
 		resp, err := l.GetUser(req)
 		if err != nil {

+ 7 - 7
internal/handler/createuserhandler.go → internal/handler/loginbyweixinhandler.go

@@ -3,27 +3,27 @@ package handler
 import (
 	"net/http"
 
+	"git.i2edu.net/i2/go-zero/rest/httpx"
 	"git.i2edu.net/i2/i2-bill-api/internal/logic"
 	"git.i2edu.net/i2/i2-bill-api/internal/svc"
 	"git.i2edu.net/i2/i2-bill-api/internal/types"
-
-	"git.i2edu.net/i2/go-zero/rest/httpx"
+	"git.i2edu.net/i2/i2-bill-api/internal/utils"
 )
 
-func CreateUserHandler(ctx *svc.ServiceContext) http.HandlerFunc {
+func LoginByWeixinHandler(ctx *svc.ServiceContext) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
-		var req types.Request
+		var req types.AuthLoginBody
 		if err := httpx.Parse(r, &req); err != nil {
 			httpx.Error(w, err)
 			return
 		}
 
-		l := logic.NewCreateUserLogic(r.Context(), ctx)
-		err := l.CreateUser(req)
+		l := logic.NewLoginByWeixinLogic(r.Context(), ctx)
+		AuthResponse, err := l.LoginByWeixin(req)
 		if err != nil {
 			httpx.Error(w, err)
 		} else {
-			httpx.Ok(w)
+			httpx.OkJson(w, utils.ReturnHTTPSuccess(AuthResponse))
 		}
 	}
 }

+ 12 - 6
internal/handler/routes.go

@@ -13,15 +13,21 @@ func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
 	engine.AddRoutes(
 		[]rest.Route{
 			{
-				Method:  http.MethodGet,
-				Path:    "/users/id/:userId",
-				Handler: GetUserHandler(serverCtx),
+				Method:  http.MethodPost,
+				Path:    "/api/auth/loginByWeixin",
+				Handler: LoginByWeixinHandler(serverCtx),
 			},
+		},
+	)
+
+	engine.AddRoutes(
+		[]rest.Route{
 			{
-				Method:  http.MethodPost,
-				Path:    "/users/create",
-				Handler: CreateUserHandler(serverCtx),
+				Method:  http.MethodGet,
+				Path:    "/api/users/id/:userId",
+				Handler: GetUserHandler(serverCtx),
 			},
 		},
+		rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
 	)
 }

+ 0 - 30
internal/logic/createuserlogic.go

@@ -1,30 +0,0 @@
-package logic
-
-import (
-	"context"
-
-	"git.i2edu.net/i2/i2-bill-api/internal/svc"
-	"git.i2edu.net/i2/i2-bill-api/internal/types"
-
-	"git.i2edu.net/i2/go-zero/core/logx"
-)
-
-type CreateUserLogic struct {
-	logx.Logger
-	ctx    context.Context
-	svcCtx *svc.ServiceContext
-}
-
-func NewCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateUserLogic {
-	return CreateUserLogic{
-		Logger: logx.WithContext(ctx),
-		ctx:    ctx,
-		svcCtx: svcCtx,
-	}
-}
-
-func (l *CreateUserLogic) CreateUser(req types.Request) error {
-	// todo: add your logic here and delete this line
-
-	return nil
-}

+ 1 - 11
internal/logic/getuserlogic.go

@@ -6,8 +6,6 @@ import (
 	"git.i2edu.net/i2/i2-bill-api/internal/svc"
 	"git.i2edu.net/i2/i2-bill-api/internal/types"
 
-	"git.i2edu.net/i2/i2-bill-erp/transformclient"
-
 	"git.i2edu.net/i2/go-zero/core/logx"
 )
 
@@ -26,13 +24,5 @@ func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) GetUserLog
 }
 
 func (l *GetUserLogic) GetUser(req types.Request) (*types.Response, error) {
-	resp, err := l.svcCtx.Transformer.Ping(l.ctx, &transformclient.Request{
-		Id: req.Id,
-	})
-	if err != nil {
-		return &types.Response{}, err
-	}
-	return &types.Response{
-		Id: resp.Id,
-	}, nil
+	return &types.Response{}, nil
 }

+ 149 - 0
internal/logic/loginbyweixinlogic.go

@@ -0,0 +1,149 @@
+package logic
+
+import (
+	"context"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	"git.i2edu.net/i2/go-zero/core/logx"
+	"git.i2edu.net/i2/go-zero/core/stores/sqlc"
+	"git.i2edu.net/i2/i2-bill-api/internal/svc"
+	"git.i2edu.net/i2/i2-bill-api/internal/types"
+	"git.i2edu.net/i2/i2-bill-api/internal/utils"
+	"git.i2edu.net/i2/i2-bill-api/model"
+	"git.i2edu.net/i2/i2-bill-erp/transformclient"
+	"github.com/dgrijalva/jwt-go"
+)
+
+type LoginByWeixinLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewLoginByWeixinLogic(ctx context.Context, svcCtx *svc.ServiceContext) LoginByWeixinLogic {
+	return LoginByWeixinLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx,
+	}
+}
+
+func (l *LoginByWeixinLogic) LoginByWeixin(req types.AuthLoginBody) (*types.AuthResponse, error) {
+	alb := req
+	userInfo, err := l.Login(alb.Code, alb.UserInfo)
+	if err != nil {
+		return nil, err
+	}
+
+	//
+	resp, err := l.svcCtx.Transformer.GetUser(l.ctx, &transformclient.Request{
+		Id: "0909",
+	})
+	if err != nil {
+		return nil, err
+	}
+	fmt.Println(resp.Roles)
+
+	user, err := l.svcCtx.UserModel.FindOneByWeiXinOpenId(userInfo.OpenID)
+	if err == sqlc.ErrNotFound {
+		newuser := model.User{Username: utils.GetUUID(), Password: "", RegisterTime: utils.GetTimestamp(),
+			RegisterIp: "", Mobile: "", WeixinOpenid: userInfo.OpenID, Avatar: userInfo.AvatarUrl, Gender: userInfo.Gender,
+			Nickname: userInfo.NickName}
+		l.svcCtx.UserModel.Insert(newuser)
+		user, _ = l.svcCtx.UserModel.FindOneByWeiXinOpenId(userInfo.OpenID)
+	}
+
+	rtnInfo := types.AuthResponse{}
+	rtnInfo.UserInfo.ID = user.Id
+	rtnInfo.UserInfo.UserName = user.Username
+	rtnInfo.UserInfo.NickName = user.Nickname
+	rtnInfo.UserInfo.Gender = user.Gender
+	rtnInfo.UserInfo.Avatar = user.Avatar
+	rtnInfo.UserInfo.Birthday = user.Birthday
+
+	user.LastLoginIp = ""
+	user.LastLoginTime = utils.GetTimestamp()
+
+	if err := l.svcCtx.UserModel.Update(*user); err == nil {
+		return nil, err
+	}
+
+	var accessExpire = l.svcCtx.Config.JwtAuth.AccessExpire
+	now := time.Now().Unix()
+	payloads := map[string]interface{}{
+		"user_id": user.Id,
+	}
+	accessToken, err := l.CreateJWT(now, l.svcCtx.Config.JwtAuth.AccessSecret, payloads, accessExpire)
+	if err != nil {
+		return nil, err
+	}
+	rtnInfo.Token = accessToken
+	return &rtnInfo, nil
+}
+
+func (l *LoginByWeixinLogic) CreateJWT(iat int64, secretKey string, payloads map[string]interface{}, seconds int64) (string, error) {
+	claims := make(jwt.MapClaims)
+	claims["exp"] = iat + seconds
+	claims["iat"] = iat
+	for k, v := range payloads {
+		claims[k] = v
+	}
+
+	token := jwt.New(jwt.SigningMethodHS256)
+	token.Claims = claims
+	return token.SignedString([]byte(secretKey))
+}
+
+func (l *LoginByWeixinLogic) Login(code string, fullUserInfo types.ResUserInfo) (*types.WXUserInfo, error) {
+
+	secret := l.svcCtx.Config.Weixin.Secret
+	appid := l.svcCtx.Config.Weixin.Appid
+
+	req := utils.Get("https://api.weixin.qq.com/sns/jscode2session")
+	req.Param("grant_type", "authorization_code")
+	req.Param("js_code", code)
+	req.Param("secret", secret)
+	req.Param("appid", appid)
+
+	var res types.WXLoginResponse
+	req.ToJSON(&res)
+
+	s := sha1.New()
+	s.Write([]byte(fullUserInfo.RawData + res.SessionKey))
+	sha1 := s.Sum(nil)
+	sha1hash := hex.EncodeToString(sha1)
+
+	if fullUserInfo.Signature != sha1hash {
+		return nil, errors.New("signature err")
+	}
+	userinfo, err := l.DecryptUserInfoData(res.SessionKey, fullUserInfo.EncryptedData, fullUserInfo.IV)
+
+	return userinfo, err
+
+}
+
+func (l *LoginByWeixinLogic) DecryptUserInfoData(sessionKey string, encryptedData string, iv string) (*types.WXUserInfo, error) {
+
+	sk, _ := base64.StdEncoding.DecodeString(sessionKey)
+	ed, _ := base64.StdEncoding.DecodeString(encryptedData)
+	i, _ := base64.StdEncoding.DecodeString(iv)
+
+	decryptedData, err := utils.AesCBCDecrypt(ed, sk, i)
+
+	if err != nil {
+		return nil, err
+	}
+
+	var wxuserinfo types.WXUserInfo
+	err = json.Unmarshal(decryptedData, &wxuserinfo)
+	if err != nil {
+		return nil, err
+	}
+	return &wxuserinfo, nil
+}

+ 4 - 0
internal/svc/servicecontext.go

@@ -2,13 +2,16 @@ package svc
 
 import (
 	"git.i2edu.net/i2/i2-bill-api/internal/config"
+	"git.i2edu.net/i2/i2-bill-api/model"
 	"git.i2edu.net/i2/i2-bill-erp/transformclient"
 
+	"git.i2edu.net/i2/go-zero/core/stores/sqlx"
 	"git.i2edu.net/i2/go-zero/zrpc"
 )
 
 type ServiceContext struct {
 	Config      config.Config
+	UserModel   model.UserModel
 	Transformer transformclient.Transform
 }
 
@@ -16,5 +19,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
 	return &ServiceContext{
 		Config:      c,
 		Transformer: transformclient.NewTransform(zrpc.MustNewClient(c.Transform)),
+		UserModel:   model.NewUserModel(sqlx.NewMysql(c.DataSource), c.Cache),
 	}
 }

+ 53 - 0
internal/types/types.go

@@ -1,6 +1,59 @@
 // Code generated by goctl. DO NOT EDIT.
 package types
 
+type WXLoginResponse struct {
+	OpenID     string `json:"openid"`
+	SessionKey string `json:"session_key"`
+	UnionID    string `json:"unionid"`
+	ErrCode    int    `json:"errcode"`
+	ErrMsg     string `json:"errmsg"`
+}
+
+type Watermark struct {
+	AppID     string `json:"appid"`
+	TimeStamp int64  `json:"timestamp"`
+}
+
+type WXUserInfo struct {
+	OpenID    string    `json:"openId,omitempty"`
+	NickName  string    `json:"nickName"`
+	AvatarUrl string    `json:"avatarUrl"`
+	Gender    int64     `json:"gender"`
+	Country   string    `json:"country"`
+	Province  string    `json:"province"`
+	City      string    `json:"city"`
+	UnionID   string    `json:"unionId,omitempty"`
+	Language  string    `json:"language"`
+	Watermark Watermark `json:"watermark,omitempty"`
+}
+
+type ResUserInfo struct {
+	UserInfo      WXUserInfo `json:"userInfo"`
+	RawData       string     `json:"rawData"`
+	Signature     string     `json:"signature"`
+	EncryptedData string     `json:"encryptedData"`
+	IV            string     `json:"iv"`
+}
+
+type AuthLoginBody struct {
+	Code     string      `json:"code"`
+	UserInfo ResUserInfo `json:"userInfo"`
+}
+
+type AuthUserInfo struct {
+	ID       int64  `json:"id"`
+	UserName string `json:"username"`
+	NickName string `json:"nickname"`
+	Gender   int64  `json:"gender"`
+	Avatar   string `json:"avatar"`
+	Birthday int64  `json:"birthday"`
+}
+
+type AuthResponse struct {
+	Token    string       `json:"token"`
+	UserInfo AuthUserInfo `json:"userInfo"`
+}
+
 type Request struct {
 	Id int64 `form:"id"`
 }

+ 62 - 0
internal/utils/crypto.go

@@ -0,0 +1,62 @@
+package utils
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/md5"
+	"encoding/base64"
+	"encoding/hex"
+	"net/url"
+)
+
+func PKCS7UnPadding(origData []byte) []byte {
+	length := len(origData)
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+func AesCBCDecrypt(encryptData, key, iv []byte) ([]byte, error) {
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		panic(err)
+	}
+
+	blockSize := block.BlockSize()
+	if len(encryptData) < blockSize {
+		panic("ciphertext too short")
+	}
+
+	if len(encryptData)%blockSize != 0 {
+		panic("ciphertext is not a multiple of the block size")
+	}
+	mode := cipher.NewCBCDecrypter(block, iv)
+
+	decryptedData := make([]byte, len(encryptData))
+	mode.CryptBlocks(decryptedData, encryptData)
+	decryptedData = PKCS7UnPadding(decryptedData)
+	return decryptedData, nil
+}
+
+func Md5(str string) string {
+	h := md5.New()
+	h.Write([]byte(str))
+	return hex.EncodeToString(h.Sum(nil))
+}
+
+func Base64Encode(str string) string {
+	return base64.StdEncoding.EncodeToString([]byte(str))
+}
+
+func Base64Decode(str string) string {
+	decodestr, _ := base64.StdEncoding.DecodeString(str)
+	return string(decodestr)
+}
+
+func UrlEncode(str string) (string, error) {
+	u, err := url.Parse(str)
+	if err != nil {
+		return "", err
+	}
+	return u.String(), nil
+}

+ 639 - 0
internal/utils/http.go

@@ -0,0 +1,639 @@
+package utils
+
+import (
+	"bytes"
+	"compress/gzip"
+	"context"
+	"crypto/tls"
+	"encoding/json"
+	"encoding/xml"
+	"io"
+	"io/ioutil"
+	"log"
+	"mime/multipart"
+	"net"
+	"net/http"
+	"net/http/cookiejar"
+	"net/http/httputil"
+	"net/url"
+	"os"
+	"path"
+	"strings"
+	"sync"
+	"time"
+
+	"gopkg.in/yaml.v2"
+)
+
+var defaultSetting = HTTPSettings{
+	UserAgent:        "beegoServer",
+	ConnectTimeout:   60 * time.Second,
+	ReadWriteTimeout: 60 * time.Second,
+	Gzip:             true,
+	DumpBody:         true,
+}
+
+var defaultCookieJar http.CookieJar
+var settingMutex sync.Mutex
+
+// it will be the last filter and execute request.Do
+var doRequestFilter = func(ctx context.Context, req *HTTPRequest) (*http.Response, error) {
+	return req.doRequest(ctx)
+}
+
+// createDefaultCookie creates a global cookiejar to store cookies.
+func createDefaultCookie() {
+	settingMutex.Lock()
+	defer settingMutex.Unlock()
+	defaultCookieJar, _ = cookiejar.New(nil)
+}
+
+// SetDefaultSetting overwrites default settings
+func SetDefaultSetting(setting HTTPSettings) {
+	settingMutex.Lock()
+	defer settingMutex.Unlock()
+	defaultSetting = setting
+}
+
+// NewRequest returns *HTTPRequest with specific method
+func NewRequest(rawurl, method string) *HTTPRequest {
+	var resp http.Response
+	u, err := url.Parse(rawurl)
+	if err != nil {
+		log.Println("Httplib:", err)
+	}
+	req := http.Request{
+		URL:        u,
+		Method:     method,
+		Header:     make(http.Header),
+		Proto:      "HTTP/1.1",
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+	}
+	return &HTTPRequest{
+		url:     rawurl,
+		req:     &req,
+		params:  map[string][]string{},
+		files:   map[string]string{},
+		setting: defaultSetting,
+		resp:    &resp,
+	}
+}
+
+// Get returns *HTTPRequest with GET method.
+func Get(url string) *HTTPRequest {
+	return NewRequest(url, "GET")
+}
+
+// Post returns *HTTPRequest with POST method.
+func Post(url string) *HTTPRequest {
+	return NewRequest(url, "POST")
+}
+
+// Put returns *HTTPRequest with PUT method.
+func Put(url string) *HTTPRequest {
+	return NewRequest(url, "PUT")
+}
+
+// Delete returns *HTTPRequest DELETE method.
+func Delete(url string) *HTTPRequest {
+	return NewRequest(url, "DELETE")
+}
+
+// Head returns *HTTPRequest with HEAD method.
+func Head(url string) *HTTPRequest {
+	return NewRequest(url, "HEAD")
+}
+
+// HTTPSettings is the http.Client setting
+type HTTPSettings struct {
+	ShowDebug        bool
+	UserAgent        string
+	ConnectTimeout   time.Duration
+	ReadWriteTimeout time.Duration
+	TLSClientConfig  *tls.Config
+	Proxy            func(*http.Request) (*url.URL, error)
+	Transport        http.RoundTripper
+	CheckRedirect    func(req *http.Request, via []*http.Request) error
+	EnableCookie     bool
+	Gzip             bool
+	DumpBody         bool
+	Retries          int // if set to -1 means will retry forever
+	RetryDelay       time.Duration
+}
+
+// HTTPRequest provides more useful methods than http.Request for requesting a url.
+type HTTPRequest struct {
+	url     string
+	req     *http.Request
+	params  map[string][]string
+	files   map[string]string
+	setting HTTPSettings
+	resp    *http.Response
+	body    []byte
+	dump    []byte
+}
+
+// GetRequest returns the request object
+func (b *HTTPRequest) GetRequest() *http.Request {
+	return b.req
+}
+
+// Setting changes request settings
+func (b *HTTPRequest) Setting(setting HTTPSettings) *HTTPRequest {
+	b.setting = setting
+	return b
+}
+
+// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
+func (b *HTTPRequest) SetBasicAuth(username, password string) *HTTPRequest {
+	b.req.SetBasicAuth(username, password)
+	return b
+}
+
+// SetEnableCookie sets enable/disable cookiejar
+func (b *HTTPRequest) SetEnableCookie(enable bool) *HTTPRequest {
+	b.setting.EnableCookie = enable
+	return b
+}
+
+// SetUserAgent sets User-Agent header field
+func (b *HTTPRequest) SetUserAgent(useragent string) *HTTPRequest {
+	b.setting.UserAgent = useragent
+	return b
+}
+
+// Debug sets show debug or not when executing request.
+func (b *HTTPRequest) Debug(isdebug bool) *HTTPRequest {
+	b.setting.ShowDebug = isdebug
+	return b
+}
+
+// Retries sets Retries times.
+// default is 0 (never retry)
+// -1 retry indefinitely (forever)
+// Other numbers specify the exact retry amount
+func (b *HTTPRequest) Retries(times int) *HTTPRequest {
+	b.setting.Retries = times
+	return b
+}
+
+// RetryDelay sets the time to sleep between reconnection attempts
+func (b *HTTPRequest) RetryDelay(delay time.Duration) *HTTPRequest {
+	b.setting.RetryDelay = delay
+	return b
+}
+
+// DumpBody sets the DumbBody field
+func (b *HTTPRequest) DumpBody(isdump bool) *HTTPRequest {
+	b.setting.DumpBody = isdump
+	return b
+}
+
+// DumpRequest returns the DumpRequest
+func (b *HTTPRequest) DumpRequest() []byte {
+	return b.dump
+}
+
+// SetTimeout sets connect time out and read-write time out for BeegoRequest.
+func (b *HTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *HTTPRequest {
+	b.setting.ConnectTimeout = connectTimeout
+	b.setting.ReadWriteTimeout = readWriteTimeout
+	return b
+}
+
+// SetTLSClientConfig sets TLS connection configuration if visiting HTTPS url.
+func (b *HTTPRequest) SetTLSClientConfig(config *tls.Config) *HTTPRequest {
+	b.setting.TLSClientConfig = config
+	return b
+}
+
+// Header adds header item string in request.
+func (b *HTTPRequest) Header(key, value string) *HTTPRequest {
+	b.req.Header.Set(key, value)
+	return b
+}
+
+// SetHost set the request host
+func (b *HTTPRequest) SetHost(host string) *HTTPRequest {
+	b.req.Host = host
+	return b
+}
+
+// SetProtocolVersion sets the protocol version for incoming requests.
+// Client requests always use HTTP/1.1.
+func (b *HTTPRequest) SetProtocolVersion(vers string) *HTTPRequest {
+	if len(vers) == 0 {
+		vers = "HTTP/1.1"
+	}
+
+	major, minor, ok := http.ParseHTTPVersion(vers)
+	if ok {
+		b.req.Proto = vers
+		b.req.ProtoMajor = major
+		b.req.ProtoMinor = minor
+	}
+
+	return b
+}
+
+// SetCookie adds a cookie to the request.
+func (b *HTTPRequest) SetCookie(cookie *http.Cookie) *HTTPRequest {
+	b.req.Header.Add("Cookie", cookie.String())
+	return b
+}
+
+// SetTransport sets the transport field
+func (b *HTTPRequest) SetTransport(transport http.RoundTripper) *HTTPRequest {
+	b.setting.Transport = transport
+	return b
+}
+
+// SetProxy sets the HTTP proxy
+// example:
+//
+//	func(req *http.Request) (*url.URL, error) {
+// 		u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
+// 		return u, nil
+// 	}
+func (b *HTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *HTTPRequest {
+	b.setting.Proxy = proxy
+	return b
+}
+
+// SetCheckRedirect specifies the policy for handling redirects.
+//
+// If CheckRedirect is nil, the Client uses its default policy,
+// which is to stop after 10 consecutive requests.
+func (b *HTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *HTTPRequest {
+	b.setting.CheckRedirect = redirect
+	return b
+}
+
+// Param adds query param in to request.
+// params build query string as ?key1=value1&key2=value2...
+func (b *HTTPRequest) Param(key, value string) *HTTPRequest {
+	if param, ok := b.params[key]; ok {
+		b.params[key] = append(param, value)
+	} else {
+		b.params[key] = []string{value}
+	}
+	return b
+}
+
+// PostFile adds a post file to the request
+func (b *HTTPRequest) PostFile(formname, filename string) *HTTPRequest {
+	b.files[formname] = filename
+	return b
+}
+
+// Body adds request raw body.
+// Supports string and []byte.
+func (b *HTTPRequest) Body(data interface{}) *HTTPRequest {
+	switch t := data.(type) {
+	case string:
+		bf := bytes.NewBufferString(t)
+		b.req.Body = ioutil.NopCloser(bf)
+		b.req.ContentLength = int64(len(t))
+	case []byte:
+		bf := bytes.NewBuffer(t)
+		b.req.Body = ioutil.NopCloser(bf)
+		b.req.ContentLength = int64(len(t))
+	}
+	return b
+}
+
+// XMLBody adds the request raw body encoded in XML.
+func (b *HTTPRequest) XMLBody(obj interface{}) (*HTTPRequest, error) {
+	if b.req.Body == nil && obj != nil {
+		byts, err := xml.Marshal(obj)
+		if err != nil {
+			return b, err
+		}
+		b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
+		b.req.ContentLength = int64(len(byts))
+		b.req.Header.Set("Content-Type", "application/xml")
+	}
+	return b, nil
+}
+
+// YAMLBody adds the request raw body encoded in YAML.
+func (b *HTTPRequest) YAMLBody(obj interface{}) (*HTTPRequest, error) {
+	if b.req.Body == nil && obj != nil {
+		byts, err := yaml.Marshal(obj)
+		if err != nil {
+			return b, err
+		}
+		b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
+		b.req.ContentLength = int64(len(byts))
+		b.req.Header.Set("Content-Type", "application/x+yaml")
+	}
+	return b, nil
+}
+
+// JSONBody adds the request raw body encoded in JSON.
+func (b *HTTPRequest) JSONBody(obj interface{}) (*HTTPRequest, error) {
+	if b.req.Body == nil && obj != nil {
+		byts, err := json.Marshal(obj)
+		if err != nil {
+			return b, err
+		}
+		b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
+		b.req.ContentLength = int64(len(byts))
+		b.req.Header.Set("Content-Type", "application/json")
+	}
+	return b, nil
+}
+
+func (b *HTTPRequest) buildURL(paramBody string) {
+	// build GET url with query string
+	if b.req.Method == "GET" && len(paramBody) > 0 {
+		if strings.Contains(b.url, "?") {
+			b.url += "&" + paramBody
+		} else {
+			b.url = b.url + "?" + paramBody
+		}
+		return
+	}
+
+	// build POST/PUT/PATCH url and body
+	if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH" || b.req.Method == "DELETE") && b.req.Body == nil {
+		// with files
+		if len(b.files) > 0 {
+			pr, pw := io.Pipe()
+			bodyWriter := multipart.NewWriter(pw)
+			go func() {
+				for formname, filename := range b.files {
+					fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
+					if err != nil {
+						log.Println("Httplib:", err)
+					}
+					fh, err := os.Open(filename)
+					if err != nil {
+						log.Println("Httplib:", err)
+					}
+					// iocopy
+					_, err = io.Copy(fileWriter, fh)
+					fh.Close()
+					if err != nil {
+						log.Println("Httplib:", err)
+					}
+				}
+				for k, v := range b.params {
+					for _, vv := range v {
+						bodyWriter.WriteField(k, vv)
+					}
+				}
+				bodyWriter.Close()
+				pw.Close()
+			}()
+			b.Header("Content-Type", bodyWriter.FormDataContentType())
+			b.req.Body = ioutil.NopCloser(pr)
+			b.Header("Transfer-Encoding", "chunked")
+			return
+		}
+
+		// with params
+		if len(paramBody) > 0 {
+			b.Header("Content-Type", "application/x-www-form-urlencoded")
+			b.Body(paramBody)
+		}
+	}
+}
+
+func (b *HTTPRequest) getResponse() (*http.Response, error) {
+	if b.resp.StatusCode != 0 {
+		return b.resp, nil
+	}
+	resp, err := b.DoRequest()
+	if err != nil {
+		return nil, err
+	}
+	b.resp = resp
+	return resp, nil
+}
+
+// DoRequest executes client.Do
+func (b *HTTPRequest) DoRequest() (resp *http.Response, err error) {
+	return b.DoRequestWithCtx(context.Background())
+}
+
+func (b *HTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Response, err error) {
+	root := doRequestFilter
+	return root(ctx, b)
+}
+
+func (b *HTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) {
+	var paramBody string
+	if len(b.params) > 0 {
+		var buf bytes.Buffer
+		for k, v := range b.params {
+			for _, vv := range v {
+				buf.WriteString(url.QueryEscape(k))
+				buf.WriteByte('=')
+				buf.WriteString(url.QueryEscape(vv))
+				buf.WriteByte('&')
+			}
+		}
+		paramBody = buf.String()
+		paramBody = paramBody[0 : len(paramBody)-1]
+	}
+
+	b.buildURL(paramBody)
+	urlParsed, err := url.Parse(b.url)
+	if err != nil {
+		return nil, err
+	}
+
+	b.req.URL = urlParsed
+
+	trans := b.setting.Transport
+
+	if trans == nil {
+		// create default transport
+		trans = &http.Transport{
+			TLSClientConfig:     b.setting.TLSClientConfig,
+			Proxy:               b.setting.Proxy,
+			Dial:                TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
+			MaxIdleConnsPerHost: 100,
+		}
+	} else {
+		// if b.transport is *http.Transport then set the settings.
+		if t, ok := trans.(*http.Transport); ok {
+			if t.TLSClientConfig == nil {
+				t.TLSClientConfig = b.setting.TLSClientConfig
+			}
+			if t.Proxy == nil {
+				t.Proxy = b.setting.Proxy
+			}
+			if t.Dial == nil {
+				t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
+			}
+		}
+	}
+
+	var jar http.CookieJar
+	if b.setting.EnableCookie {
+		if defaultCookieJar == nil {
+			createDefaultCookie()
+		}
+		jar = defaultCookieJar
+	}
+
+	client := &http.Client{
+		Transport: trans,
+		Jar:       jar,
+	}
+
+	if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" {
+		b.req.Header.Set("User-Agent", b.setting.UserAgent)
+	}
+
+	if b.setting.CheckRedirect != nil {
+		client.CheckRedirect = b.setting.CheckRedirect
+	}
+
+	if b.setting.ShowDebug {
+		dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
+		if err != nil {
+			log.Println(err.Error())
+		}
+		b.dump = dump
+	}
+	// retries default value is 0, it will run once.
+	// retries equal to -1, it will run forever until success
+	// retries is setted, it will retries fixed times.
+	// Sleeps for a 400ms inbetween calls to reduce spam
+	for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
+		resp, err = client.Do(b.req)
+		if err == nil {
+			break
+		}
+		time.Sleep(b.setting.RetryDelay)
+	}
+	return resp, err
+}
+
+// String returns the body string in response.
+// Calls Response inner.
+func (b *HTTPRequest) String() (string, error) {
+	data, err := b.Bytes()
+	if err != nil {
+		return "", err
+	}
+
+	return string(data), nil
+}
+
+// Bytes returns the body []byte in response.
+// Calls Response inner.
+func (b *HTTPRequest) Bytes() ([]byte, error) {
+	if b.body != nil {
+		return b.body, nil
+	}
+	resp, err := b.getResponse()
+	if err != nil {
+		return nil, err
+	}
+	if resp.Body == nil {
+		return nil, nil
+	}
+	defer resp.Body.Close()
+	if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" {
+		reader, err := gzip.NewReader(resp.Body)
+		if err != nil {
+			return nil, err
+		}
+		b.body, err = ioutil.ReadAll(reader)
+		return b.body, err
+	}
+	b.body, err = ioutil.ReadAll(resp.Body)
+	return b.body, err
+}
+
+// ToFile saves the body data in response to one file.
+// Calls Response inner.
+func (b *HTTPRequest) ToFile(filename string) error {
+	resp, err := b.getResponse()
+	if err != nil {
+		return err
+	}
+	if resp.Body == nil {
+		return nil
+	}
+	defer resp.Body.Close()
+	err = pathExistAndMkdir(filename)
+	if err != nil {
+		return err
+	}
+	f, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	_, err = io.Copy(f, resp.Body)
+	return err
+}
+
+// Check if the file directory exists. If it doesn't then it's created
+func pathExistAndMkdir(filename string) (err error) {
+	filename = path.Dir(filename)
+	_, err = os.Stat(filename)
+	if err == nil {
+		return nil
+	}
+	if os.IsNotExist(err) {
+		err = os.MkdirAll(filename, os.ModePerm)
+		if err == nil {
+			return nil
+		}
+	}
+	return err
+}
+
+// ToJSON returns the map that marshals from the body bytes as json in response.
+// Calls Response inner.
+func (b *HTTPRequest) ToJSON(v interface{}) error {
+	data, err := b.Bytes()
+	if err != nil {
+		return err
+	}
+	return json.Unmarshal(data, v)
+}
+
+// ToXML returns the map that marshals from the body bytes as xml in response .
+// Calls Response inner.
+func (b *HTTPRequest) ToXML(v interface{}) error {
+	data, err := b.Bytes()
+	if err != nil {
+		return err
+	}
+	return xml.Unmarshal(data, v)
+}
+
+// ToYAML returns the map that marshals from the body bytes as yaml in response .
+// Calls Response inner.
+func (b *HTTPRequest) ToYAML(v interface{}) error {
+	data, err := b.Bytes()
+	if err != nil {
+		return err
+	}
+	return yaml.Unmarshal(data, v)
+}
+
+// Response executes request client gets response manually.
+func (b *HTTPRequest) Response() (*http.Response, error) {
+	return b.getResponse()
+}
+
+// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
+func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
+	return func(netw, addr string) (net.Conn, error) {
+		conn, err := net.DialTimeout(netw, addr, cTimeout)
+		if err != nil {
+			return nil, err
+		}
+		err = conn.SetDeadline(time.Now().Add(rwTimeout))
+		return conn, err
+	}
+}

+ 16 - 0
internal/utils/resp.go

@@ -0,0 +1,16 @@
+package utils
+
+type HTTPData struct {
+	ErrNo  int         `json:"errno"`
+	ErrMsg string      `json:"errmsg"`
+	Data   interface{} `json:"data"`
+}
+
+func ReturnHTTPSuccess(val interface{}) HTTPData {
+	rtndata := HTTPData{
+		ErrNo:  0,
+		ErrMsg: "",
+		Data:   val,
+	}
+	return rtndata
+}

+ 21 - 0
internal/utils/utils.go

@@ -0,0 +1,21 @@
+package utils
+
+import (
+	"strconv"
+	"time"
+
+	uuid "github.com/satori/go.uuid"
+)
+
+func GetUUID() string {
+	return uuid.NewV4().String()
+}
+
+//the result likes 1423361979
+func GetTimestamp() int64 {
+	return time.Now().Unix()
+}
+
+func Int2String(val int64) string {
+	return strconv.Itoa(int(val))
+}

+ 19 - 0
model/user.sql

@@ -0,0 +1,19 @@
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+  `username` varchar(60) NOT NULL DEFAULT '',
+  `password` varchar(32) NOT NULL DEFAULT '',
+  `gender` tinyint(1) unsigned NOT NULL DEFAULT '0',
+  `birthday` int(11) unsigned NOT NULL DEFAULT '0',
+  `register_time` int(11) unsigned NOT NULL DEFAULT '0',
+  `last_login_time` int(11) unsigned NOT NULL DEFAULT '0',
+  `last_login_ip` varchar(255) NOT NULL DEFAULT '',
+  `user_level_id` tinyint(3) unsigned NOT NULL DEFAULT '0',
+  `nickname` varchar(60) NOT NULL,
+  `mobile` varchar(20) NOT NULL,
+  `register_ip` varchar(255) NOT NULL DEFAULT '',
+  `avatar` varchar(255) NOT NULL DEFAULT '',
+  `weixin_openid` varchar(50) NOT NULL DEFAULT '',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `user_name` (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

+ 161 - 0
model/usermodel.go

@@ -0,0 +1,161 @@
+package model
+
+import (
+	"database/sql"
+	"fmt"
+	"strings"
+
+	"git.i2edu.net/i2/go-zero/core/stores/cache"
+	"git.i2edu.net/i2/go-zero/core/stores/sqlc"
+	"git.i2edu.net/i2/go-zero/core/stores/sqlx"
+	"git.i2edu.net/i2/go-zero/core/stringx"
+	"git.i2edu.net/i2/go-zero/tools/goctl/model/sql/builderx"
+)
+
+var (
+	userFieldNames          = builderx.RawFieldNames(&User{})
+	userRows                = strings.Join(userFieldNames, ",")
+	userRowsExpectAutoSet   = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
+	userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
+
+	cacheUserIdPrefix           = "cache:user:id:"
+	cacheUserUsernamePrefix     = "cache:user:username:"
+	cacheUserWeiXinOpenIdPrefix = "cache:user:weixin_openid:"
+)
+
+type (
+	UserModel interface {
+		Insert(data User) (sql.Result, error)
+		FindOne(id int64) (*User, error)
+		FindOneByUsername(username string) (*User, error)
+		FindOneByWeiXinOpenId(openid string) (*User, error)
+		Update(data User) error
+		Delete(id int64) error
+	}
+
+	defaultUserModel struct {
+		sqlc.CachedConn
+		table string
+	}
+
+	User struct {
+		Mobile        string `db:"mobile"`
+		Avatar        string `db:"avatar"`
+		WeixinOpenid  string `db:"weixin_openid"`
+		Password      string `db:"password"`
+		Birthday      int64  `db:"birthday"`
+		RegisterTime  int64  `db:"register_time"`
+		LastLoginTime int64  `db:"last_login_time"`
+		Nickname      string `db:"nickname"`
+		Id            int64  `db:"id"`
+		Username      string `db:"username"`
+		Gender        int64  `db:"gender"`
+		UserLevelId   int64  `db:"user_level_id"`
+		RegisterIp    string `db:"register_ip"`
+		LastLoginIp   string `db:"last_login_ip"`
+	}
+)
+
+func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
+	return &defaultUserModel{
+		CachedConn: sqlc.NewConn(conn, c),
+		table:      "`user`",
+	}
+}
+
+func (m *defaultUserModel) Insert(data User) (sql.Result, error) {
+	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, data.Username)
+	ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
+		query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet)
+		return conn.Exec(query, data.Mobile, data.Avatar, data.WeixinOpenid, data.Password, data.Birthday, data.RegisterTime, data.LastLoginTime, data.Nickname, data.Username, data.Gender, data.UserLevelId, data.RegisterIp, data.LastLoginIp)
+	}, userUsernameKey)
+	return ret, err
+}
+
+func (m *defaultUserModel) FindOne(id int64) (*User, error) {
+	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
+	var resp User
+	err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
+		query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRows, m.table)
+		return conn.QueryRow(v, query, id)
+	})
+	switch err {
+	case nil:
+		return &resp, nil
+	case sqlc.ErrNotFound:
+		return nil, ErrNotFound
+	default:
+		return nil, err
+	}
+}
+
+func (m *defaultUserModel) FindOneByWeiXinOpenId(id string) (*User, error) {
+	userIdKey := fmt.Sprintf("%s%v", cacheUserWeiXinOpenIdPrefix, id)
+	var resp User
+	err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
+		query := fmt.Sprintf("select %s from %s where `weixin_openid` = ? limit 1", userRows, m.table)
+		return conn.QueryRow(v, query, id)
+	})
+	switch err {
+	case nil:
+		return &resp, nil
+	case sqlc.ErrNotFound:
+		return nil, ErrNotFound
+	default:
+		return nil, err
+	}
+}
+
+func (m *defaultUserModel) FindOneByUsername(username string) (*User, error) {
+	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, username)
+	var resp User
+	err := m.QueryRowIndex(&resp, userUsernameKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
+		query := fmt.Sprintf("select %s from %s where `username` = ? limit 1", userRows, m.table)
+		if err := conn.QueryRow(&resp, query, username); err != nil {
+			return nil, err
+		}
+		return resp.Id, nil
+	}, m.queryPrimary)
+	switch err {
+	case nil:
+		return &resp, nil
+	case sqlc.ErrNotFound:
+		return nil, ErrNotFound
+	default:
+		return nil, err
+	}
+}
+
+func (m *defaultUserModel) Update(data User) error {
+	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
+	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, data.Username)
+	_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
+		query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder)
+		return conn.Exec(query, data.Mobile, data.Avatar, data.WeixinOpenid, data.Password, data.Birthday, data.RegisterTime, data.LastLoginTime, data.Nickname, data.Username, data.Gender, data.UserLevelId, data.RegisterIp, data.LastLoginIp, data.Id)
+	}, userIdKey, userUsernameKey)
+	return err
+}
+
+func (m *defaultUserModel) Delete(id int64) error {
+	data, err := m.FindOne(id)
+	if err != nil {
+		return err
+	}
+
+	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
+	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, data.Username)
+	_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
+		query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
+		return conn.Exec(query, id)
+	}, userUsernameKey, userIdKey)
+	return err
+}
+
+func (m *defaultUserModel) formatPrimary(primary interface{}) string {
+	return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
+}
+
+func (m *defaultUserModel) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
+	query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRows, m.table)
+	return conn.QueryRow(v, query, primary)
+}

+ 5 - 0
model/vars.go

@@ -0,0 +1,5 @@
+package model
+
+import "git.i2edu.net/i2/go-zero/core/stores/sqlx"
+
+var ErrNotFound = sqlx.ErrNotFound

+ 3 - 0
start.sh

@@ -4,5 +4,8 @@ GO111MODULE=on go get -u git.i2edu.net/i2/go-zero/tools/goctl
 ### GEN API CODE
 goctl api go -api i2bill.api -dir .
 
+### GEN MODEL
+goctl model mysql ddl -c -src model/user.sql -dir model
+
 ### RUN API GATEWAY
 go run i2bill.go -f etc/i2bill-api.yaml