{"id":360528,"date":"2024-05-21T00:55:07","date_gmt":"2024-05-21T00:55:07","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=360528"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=360528","title":{"rendered":"<span>\u041f\u0438\u0448\u0435\u043c gRPC \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Go \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/pb\/n9\/xn\/pbn9xn0jxtpzgmvfsjhtgqwvt4s.jpeg\" alt=\"\u041f\u0438\u0448\u0435\u043c gRPC \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Go \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438\" data-src=\"https:\/\/habrastorage.org\/webt\/pb\/n9\/xn\/pbn9xn0jxtpzgmvfsjhtgqwvt4s.jpeg\" data-blurred=\"true\"\/><\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 gRPC \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Go \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439, \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u043a \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0443. \u041c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043a\u0430\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c, \u0442\u0430\u043a \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043c\u044b \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u043c\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u2014 URL Shortener, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0443 \u043c\u0435\u043d\u044f \u0442\u0430\u043a\u0436\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/habr.com\/ru\/companies\/selectel\/articles\/747738\/\">\u0441\u0442\u0430\u0442\u044c\u044f<\/a> \u0438 <a href=\"https:\/\/youtu.be\/rCJvW2xgnk0?si=_JddTOCm1Y-xCgBx\" rel=\"nofollow noopener noreferrer\">\u0432\u0438\u0434\u0435\u043e-\u0433\u0430\u0439\u0434 \u043d\u0430 \u044e\u0442\u0443\u0431\u0435<\/a>. \u041f\u043e\u043f\u0443\u0442\u043d\u043e \u043c\u044b \u043f\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u043c\u0441\u044f \u0441 \u0431\u0430\u0437\u043e\u0432\u044b\u043c\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u0430\u043c\u0438 \u043a \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u0418 \u0432 \u043a\u043e\u043d\u0446\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e GitHub Actions \u043d\u0430 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440.<\/p>\n<p>  <\/p>\n<blockquote><p><a href=\"https:\/\/youtu.be\/EURjTg5fw-E\" rel=\"nofollow noopener noreferrer\">\u0412\u0438\u0434\u0435\u043e-\u0432\u0435\u0440\u0441\u0438\u044f<\/a> \u044d\u0442\u043e\u0433\u043e \u0433\u0430\u0439\u0434\u0430 \u0441 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u043c\u0438 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438<\/p><\/blockquote>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430: <a href=\"https:\/\/github.com\/GolangLessons\/sso\" rel=\"nofollow noopener noreferrer\">https:\/\/github.com\/GolangLessons\/sso<\/a><\/p>\n<p>  <\/p>\n<p>\u0418\u0442\u043e\u0433\u043e, \u043d\u0430\u0448 \u043f\u043b\u0430\u043d:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043d\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441<\/li>\n<li>\u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f \u0441 \u0431\u0430\u0437\u043e\u0432\u044b\u043c\u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u2014 \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u044b\u043b\u043e \u0441\u043a\u0443\u0447\u043d\u043e<\/li>\n<li>\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u0432 \u043f\u0440\u043e\u0434 \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0440\u0443\u043a\u0430\u043c\u0438 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043b\u0435\u043d\u044c<\/li>\n<li>\u041f\u043e\u0434\u0440\u0443\u0436\u0438\u043c \u0435\u0433\u043e \u0441 \u0443\u0436\u0435 \u0433\u043e\u0442\u043e\u0432\u044b\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c <a href=\"https:\/\/github.com\/GolangLessons\/url-shortener\" rel=\"nofollow noopener noreferrer\">URL Shortener<\/a> \u2014 \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u043c\u044b\u0441\u043b<\/li>\n<li>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043a \u0441\u0432\u043e\u0438\u043c \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430\u043c.<\/p>\n<p>  <\/p>\n<p><strong>\u041a\u0440\u0430\u0442\u043a\u043e \u043e\u0431\u043e \u043c\u043d\u0435<\/strong>: \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 <a href=\"https:\/\/t.me\/ntuzov\" rel=\"nofollow noopener noreferrer\">\u041d\u0438\u043a\u043e\u043b\u0430\u0439 \u0422\u0443\u0437\u043e\u0432<\/a>, \u044f \u043c\u043d\u043e\u0433\u043e \u043b\u0435\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043d\u0430 Go, \u043e\u0447\u0435\u043d\u044c \u043b\u044e\u0431\u043b\u044e \u044d\u0442\u043e\u0442 \u044f\u0437\u044b\u043a. \u0422\u0430\u043a\u0436\u0435 \u0432\u0435\u0434\u0443 \u0441\u0432\u043e\u0439 <a href=\"https:\/\/www.youtube.com\/@nikolay_tuzov\/featured\" rel=\"nofollow noopener noreferrer\">YouTube-\u043a\u0430\u043d\u0430\u043b<\/a>.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p><b>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e, \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c:<\/b><\/p>\n<p>  <\/p>\n<p>\u2192 <a href=\"#1\">\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430<\/a><br \/>  \u2192 <a href=\"#2\">\u041a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 Protobuf<\/a><br \/>  \u2192 <a href=\"#3\">\u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f<\/a><br \/>  \u2192 <a href=\"#4\">gRPC \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/a><br \/>  \u2192 <a href=\"#5\">\u0421\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u2014 Auth<\/a><br \/>  \u2192 <a href=\"#6\">\u0421\u043b\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/a><br \/>  \u2192 <a href=\"#7\">\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432\u043e\u0435\u0434\u0438\u043d\u043e<\/a><br \/>  \u2192 <a href=\"#8\">\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/a><br \/>  \u2192 <a href=\"#9\">\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c<\/a><br \/>  \u2192 <a href=\"#10\">\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u2014 GitHub Actions<\/a><br \/>  \u2192 <a href=\"#11\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/a><\/p>\n<p>  <\/p>\n<h3 id=\"vazhnye-vstupitelnye-ogovorki\">\u0412\u0430\u0436\u043d\u044b\u0435 \u0432\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043e\u0433\u043e\u0432\u043e\u0440\u043a\u0438<\/h3>\n<p>  <\/p>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u043e\u0433\u0440\u043e\u043c\u043d\u0430\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043d\u0435\u0439 \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438, \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0438 \u0438 \u043d\u0435\u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438. \u0411\u0443\u0434\u0443 \u043e\u0447\u0435\u043d\u044c \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u0435\u043d, \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0430 \u043d\u0438\u0445 \u0443\u043a\u0430\u0436\u0435\u0442\u0435 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0447\u0442\u0435\u043d\u0438\u044f. \u042f \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u0432\u0441\u0451 \u044d\u0442\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e \u043c\u0435\u0440\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439.<\/p>\n<p>  <\/p>\n<p>\u042f \u0431\u0443\u0434\u0443 \u0441\u0442\u0430\u0440\u0430\u0442\u044c\u0441\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434 \u043a\u0430\u043a \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0435, \u0441 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0432\u0441\u0451 \u043f\u043e-\u0432\u0437\u0440\u043e\u0441\u043b\u043e\u043c\u0443. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043a\u0430\u043a \u043c\u043e\u0438 \u0431\u043e\u0435\u0432\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b. \u041d\u043e, \u0431\u0443\u0434\u0443\u0447\u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043d\u044b\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c \u0441\u0442\u0430\u0442\u044c\u0438, \u044f \u0432\u0441\u0451 \u0436\u0435 \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u043c\u0435\u0441\u0442\u0430\u043c\u0438 \u0441\u0440\u0435\u0437\u0430\u0442\u044c \u0443\u0433\u043b\u044b \u0438 \u043e\u0442\u0441\u0435\u043a\u0430\u0442\u044c \u043b\u0438\u0448\u043d\u0435\u0435 \u2014 \u0438\u043d\u0430\u0447\u0435 \u044d\u0442\u043e \u0431\u044b\u043b\u0430 \u0431\u044b \u043d\u0435 \u0441\u0442\u0430\u0442\u044c\u044f, \u0430 \u0446\u0435\u043b\u0430\u044f \u043a\u043d\u0438\u0433\u0430. \u041d\u043e \u044f \u0431\u0443\u0434\u0443 \u0441\u0442\u0430\u0440\u0430\u0442\u044c\u0441\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432\u0430\u043c \u043d\u0430 \u0442\u0430\u043a\u0438\u0435 \u043c\u0435\u0441\u0442\u0430, \u0438 \u0431\u0443\u0434\u0443 \u0434\u0430\u0432\u0430\u0442\u044c \u0441\u043e\u0432\u0435\u0442\u044b \u043f\u043e \u0438\u0445 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u043a\u0435.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u044e \u0432\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u2014 \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f, \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u043f\u0440\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u2014 \u043f\u043e\u0431\u043e\u0447\u043d\u043e\u0435. \u041c\u044b \u043d\u0435 \u043f\u0438\u0448\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 Auth, \u043c\u044b \u043d\u0435 \u043e\u0431\u0441\u0443\u0436\u0434\u0430\u0435\u043c \u043b\u0443\u0447\u0448\u0438\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044b \u043a \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435, \u044d\u0442\u043e \u043b\u0438\u0448\u044c \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u0435 \u0431\u043e\u043d\u0443\u0441\u044b. \u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0441\u044f \u0438 \u0432 \u044d\u0442\u0438 \u0442\u0435\u043c\u044b \u0442\u043e\u0436\u0435, \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0434\u0430\u043b\u0435\u0435 \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, \u043a\u043d\u0438\u0433\u0438 \u0438 \u043f\u0440.<\/p>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0432 \u043a\u043e\u0434\u0435 \u0431\u0443\u0434\u0443\u0442 \u0434\u0432\u0443\u0445 \u0442\u0438\u043f\u043e\u0432:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c: \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438<\/li>\n<li>\u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c: \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u043a\u043e\u0434\u0430<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c, \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u2014 \u044d\u0442\u043e \u0447\u0430\u0441\u0442\u044c \u043c\u043e\u0435\u0439 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b (godoc \u0438 \u043f\u0440\u043e\u0447\u0438\u0435), \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0442\u0430\u043a\u0436\u0435 \u0438 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438. \u0420\u0443\u0441\u0441\u043a\u0438\u0435 \u0436\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u2014 \u043f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438, \u0438\u0445 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438.<\/p>\n<p>  <\/p>\n<h3 id=\"sso-ili-auth\">SSO \u0438\u043b\u0438 Auth?<\/h3>\n<p>  <\/p>\n<p>\u041e\u0431\u044b\u0447\u043d\u043e \u0442\u0435\u0440\u043c\u0438\u043d\u043e\u043c Auth \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u0430 SSO \u043d\u0435\u0447\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u0449\u0435\u0435 \u2014 \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u043f\u0440\u0430\u0432\u0430\u043c\u0438 (permissions), \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u0434\u0440.<\/p>\n<p>  <\/p>\n<p>\u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0443 \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u0442\u0438\u043f\u043e\u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0435\u0441\u0442\u044c \u0438 \u0431\u043e\u043b\u0435\u0435 \u0441\u0442\u0440\u043e\u0433\u0438\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f, \u043d\u043e \u043a\u043e\u0433\u0434\u0430 \u044f \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u043b \u044d\u0442\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435, \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u044b\u043b\u0438 \u0440\u0430\u0437\u043c\u044b\u0442\u044b \u0438\u043b\u0438 \u0432\u043e\u0432\u0441\u0435 \u0441\u0442\u0438\u0440\u0430\u043b\u0438\u0441\u044c.<\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0432\u043d\u0435\u0441\u0442\u0438 \u044f\u0441\u043d\u043e\u0441\u0442\u044c, \u0442\u0435\u0440\u043c\u0438\u043d\u043e\u043c SSO \u044f \u043d\u0430\u0437\u044b\u0432\u0430\u044e \u0441\u0435\u0440\u0432\u0438\u0441, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u044e\u0449\u0438\u0439 \u0432 \u0441\u0435\u0431\u0435 \u0442\u0440\u0438 \u0432\u0430\u0436\u043d\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f (Auth)<\/li>\n<li>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043f\u0435\u0440\u043c\u0438\u0448\u0435\u043d\u043d\u0430\u043c\u0438 (Permissions)<\/li>\n<li>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 (User Info)<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u043d\u043e \u044f \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u044e \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u0430\u043b\u044c\u0448\u0435 \u0432 \u0431\u0443\u0434\u0443\u0449\u0438\u0445 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 \/ \u0440\u043e\u043b\u0438\u043a\u0430\u0445, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0443 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u043a\u0430\u043a SSO. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435, \u0442\u043e \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u043d\u0430 <a href=\"https:\/\/t.me\/ntuzov\" rel=\"nofollow noopener noreferrer\">\u043c\u043e\u0439 Telegram-\u043a\u0430\u043d\u0430\u043b<\/a>, \u0442.\u043a. \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u044e \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043b\u043e\u0449\u0430\u0434\u043a\u0430\u0445, \u0430 \u043e\u0431\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431\u043e \u0432\u0441\u0435\u0445 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044f\u0445 \u043f\u0438\u0448\u0443 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043d\u0451\u043c.<\/p>\n<p>  <\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0447\u0430\u0441\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 SSO \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u0442.\u043a. \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u0443 \u0432\u0430\u0441 \u0442\u043e\u0447\u043d\u043e \u0431\u0443\u0434\u0443\u0442 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u043d\u0430\u043d\u0438\u044f \u0438 \u043d\u0430\u0432\u044b\u043a\u0438, \u0435\u0441\u043b\u0438 \u043e\u0441\u0438\u043b\u0438\u0442\u0435 \u0434\u043e \u043a\u043e\u043d\u0446\u0430.<\/p>\n<p>  <a name=\"1\"><\/a>  <\/p>\n<h2 id=\"arhitektura\">\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430<\/h2>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 (SSO) \u2014 \u044d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u0438 \u043e\u0431\u044a\u0451\u043c\u043d\u044b\u0435 \u0442\u0435\u043c\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u043a\u0430\u043a \u044f \u043f\u0438\u0441\u0430\u043b \u0432\u044b\u0448\u0435, \u044f \u0431\u0443\u0434\u0443 \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u0441\u0440\u0435\u0437\u0430\u0442\u044c \u0443\u0433\u043b\u044b, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0440\u0430\u0441\u0442\u044f\u0433\u0438\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u044e \u0434\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0430 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u043a\u043d\u0438\u0433\u0438. \u041d\u043e \u044f \u0431\u0443\u0434\u0443 \u0434\u0430\u0432\u0430\u0442\u044c \u0441\u043e\u0432\u0435\u0442\u044b, \u0441\u043b\u0435\u0434\u0443\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0436\u0435 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u043a\u0430\u0447\u0430\u0442\u044c \u0432\u0430\u0448\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u044b (\u043a\u0430\u043a \u0441\u0435\u0440\u0432\u0435\u0440, \u0442\u0430\u043a \u0438 \u043a\u043b\u0438\u0435\u043d\u0442). \u042d\u0442\u043e \u0442\u0430\u043a\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0445\u043e\u0440\u043e\u0448\u0438\u043c \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u0435\u043c \u2013 \u043f\u043e\u043c\u043d\u0438\u0442\u0435, \u0447\u0442\u043e \u0432 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u0438 \u0432\u0430\u0436\u043d\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430!<\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0431\u0443\u0434\u0435\u043c \u0436\u0435\u0440\u0442\u0432\u043e\u0432\u0430\u0442\u044c:<\/p>\n<p>  <\/p>\n<ul>\n<li><strong>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u0430<\/strong>. \u0421\u0430\u043c\u0430 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0432 \u0446\u0435\u043b\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u0430 \u043a \u0431\u043e\u0435\u0432\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443. \u0416\u0435\u0440\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0436\u0435 \u0431\u0443\u0434\u0435\u043c \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u043e\u0431\u0432\u044f\u0437\u043a\u0430\u043c\u0438: \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433, lifecycle, \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0438 \u0442.\u043f. (\u043e\u043d\u043e \u0431\u0443\u0434\u0435\u0442, \u043d\u043e \u043f\u043e \u043c\u0438\u043d\u0438\u043c\u0443\u043c\u0443)<\/li>\n<li><strong>\u0421\u0445\u0435\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/strong>. \u0410 \u0432\u043e\u0442 \u043e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0430, \u0442.\u043a. \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u043e\u043a\u0443\u0441 \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u043d\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u041d\u043e \u0442\u0435\u043c \u043d\u0435 \u043c\u0435\u043d\u0435\u0435, \u0435\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 \u0445\u0432\u0430\u0442\u0438\u0442 \u0434\u043b\u044f \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0432\u0430\u0448\u0438\u0445 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432, \u0430 \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0433\u043e \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0434\u0430\u043c \u0441\u043e\u0432\u0435\u0442\u044b<\/li>\n<\/ul>\n<p>  <\/p>\n<h3 id=\"obschaya-shema-proekta---kak-budet-rabotat-avtorizaciya\">\u041e\u0431\u0449\u0430\u044f \u0441\u0445\u0435\u043c\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u2014 \u043a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f<\/h3>\n<p>  <\/p>\n<p>\u0414\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043b\u0438\u0446\u0430:<\/p>\n<p>  <\/p>\n<ul>\n<li><strong>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c<\/strong> (User) \u2014 \u0447\u0435\u043b\u043e\u0432\u0435\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c\u0441\u044f, \u0442.\u043a. \u043e\u043d \u0445\u043e\u0447\u0435\u0442 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430\u0448\u0438\u043c URL Shortener&#8217;\u043e\u043c<\/li>\n<li><strong>URL Shortener<\/strong> \u2014 \u0441\u0435\u0440\u0432\u0438\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c SSO<\/li>\n<li><strong>\u0421\u0435\u0440\u0432\u0435\u0440 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/strong> (SSO) \u2014 \u0441\u0435\u0440\u0432\u0438\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u0440\u0430\u0432\u0430\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0442.\u043f.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c:<\/p>\n<p>  <\/p>\n<ul>\n<li>User (\u0438\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0435 \u0438\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435) \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432 SSO, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c JWT \u0442\u043e\u043a\u0435\u043d \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/li>\n<li>\u0421 \u044d\u0442\u0438\u043c \u0442\u043e\u043a\u0435\u043d\u043e\u043c \u043e\u043d \u0438\u0434\u0451\u0442 \u0432 URL Shortener, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u2014 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0435 \u0441\u0441\u044b\u043b\u043a\u0438, \u0443\u0434\u0430\u043b\u044f\u0442\u044c \u0438\u0445 \u0438 \u0442.\u043f.<\/li>\n<li>URL Shortener \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u0434\u043e\u0441\u0442\u0430\u0451\u0442 \u0438\u0437 \u043d\u0435\u0433\u043e \u0442\u043e\u043a\u0435\u043d, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u0442 \u043a\u0442\u043e \u043f\u0440\u0438\u0448\u0435\u043b, \u0438 \u0447\u0442\u043e \u0435\u043c\u0443 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043e \u0434\u0435\u043b\u0430\u0442\u044c<\/li>\n<\/ul>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/bc\/hr\/4a\/bchr4agq7ngokzu9tujqf_ujtzs.png\" alt=\"\u0421\u0445\u0435\u043c\u0430, \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c (Client), SSO \u0438 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430\" data-src=\"https:\/\/habrastorage.org\/webt\/bc\/hr\/4a\/bchr4agq7ngokzu9tujqf_ujtzs.png\"\/><br \/>  <em>\u0421\u0445\u0435\u043c\u0430, \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c (Client), SSO \u0438 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430<\/em><\/p>\n<p>  <\/p>\n<p>\u0412\u0430\u0436\u043d\u044b\u0435 \u0432\u0435\u0449\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443 \u043d\u0430\u0441 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442:<\/p>\n<p>  <\/p>\n<ul>\n<li><strong>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 JWT<\/strong> \u2014 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432\u0435\u0440\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432 \u043d\u0451\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f. \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c, \u0442.\u043a. JWT \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0438 \u043f\u043e\u0434\u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0433\u043e \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f (\u043e\u0431 \u044d\u0442\u043e\u043c \u043d\u0438\u0436\u0435)<\/li>\n<li><strong>\u041e\u0442\u0437\u044b\u0432 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/strong> \u2014 \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u0432\u0435\u0440\u0438\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432 JWT, \u043c\u044b \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u043c \u0440\u0430\u0437\u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043e &#171;\u043f\u0440\u043e\u0442\u0443\u0445\u0430\u043d\u0438\u044f&#187; \u0435\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430. \u042d\u0442\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u043b\u043e \u0431\u044b \u0441\u0442\u0430\u0442\u044c\u044e, \u0432\u0435\u0434\u044c \u043d\u0430\u043c \u0431\u044b \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u043e\u0441\u044c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u0438 \u0432 SSO, \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u0447\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043e\u0442 URL Shortener \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u0434\u0440.<\/li>\n<li><strong>\u0413\u0438\u0431\u043a\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0440\u043e\u043b\u0435\u0439 \u0438 \u043f\u0435\u0440\u043c\u0438\u0448\u0435\u043d\u043e\u0432<\/strong> \u2014 \u044d\u0442\u043e \u0442\u0435\u043c\u0430 \u0434\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \/ \u0432\u0438\u0434\u0435\u043e<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0435\u0440\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 JWT? \u041e\u0442\u0432\u0435\u0442 \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0435\u0433\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435. \u042f \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u043b\u0438\u0448\u044c \u043a\u0440\u0430\u0442\u043a\u0438\u0439 \u043b\u0438\u043a\u0431\u0435\u0437 \u043e\u0431 \u044d\u0442\u043e\u043c, \u043d\u043e \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041a\u0440\u0430\u0442\u043a\u0438\u0439 \u043b\u0438\u043a\u0431\u0435\u0437 \u043f\u043e JWT<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<blockquote><p>JWT \u2014 \u044d\u0442\u043e \u0444\u043e\u0440\u043c\u0430\u0442 \u0442\u043e\u043a\u0435\u043d\u0430, \u0441\u043e\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u0438\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430, payload \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u0438. <\/p><\/blockquote>\n<p>\u0412 \u0437\u0430\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435 \u043e\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432\u043e\u0442 \u0442\u0430\u043a: <\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfaWQiOjEsImVtYWlsIjoiYXNkYWRzQGFzZC5jb20iLCJleHAiOjE2OTY5NTExMTgsInVpZCI6NjR9.b58TXBm1NKnVw0FvWyb5KFkG35WdB7JXeCiMWu8rNw0<\/code><\/pre>\n<p>  <\/p>\n<p>\u0410 \u0432 \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/fp\/hb\/ik\/fphbikbs5qkyooh6zfjs3sjxl94.png\" alt=\"JWT \u0442\u043e\u043a\u0435\u043d \u0432 \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435\" data-src=\"https:\/\/habrastorage.org\/webt\/fp\/hb\/ik\/fphbikbs5qkyooh6zfjs3sjxl94.png\"\/><\/p>\n<p>  <\/p>\n<p>\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u041d\u0415 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0430, \u0438 \u043b\u044e\u0431\u043e\u0439 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0439 \u0431\u0435\u0437 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u043c\u043e\u0436\u0435\u0442 \u0442\u0443\u0434\u0430 \u0437\u0430\u0433\u043b\u044f\u043d\u0443\u0442\u044c. \u041d\u043e \u0432\u043e\u0442 \u043f\u043e\u0434\u0434\u0435\u043b\u0430\u0442\u044c payload (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0434\u0430\u0442\u044c \u0441\u0435\u0431\u044f \u0437\u0430 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f) \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f, \u0442.\u043a. \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0438\u0437 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0438 payload. \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0435\u0441\u043b\u0438 \u043c\u044b \u043c\u0435\u043d\u044f\u0435\u043c payload, \u0442\u043e \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u0442\u043e\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0441\u044f, \u0438\u043d\u0430\u0447\u0435 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e.<\/p>\n<p>  <\/p>\n<p>\u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u2014 \u0443\u043a\u0440\u0430\u0441\u0442\u044c \u0443 \u043d\u0430\u0441 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 (\u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439) \u043a\u043b\u044e\u0447. \u042d\u0442\u043e\u0442 \u043a\u043b\u044e\u0447 \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0438 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 (\u0442.\u0435. URL Shortener). \u042d\u0442\u043e\u0442 \u043a\u043b\u044e\u0447 \u043d\u0443\u0436\u043d\u043e \u0431\u0435\u0440\u0435\u0447\u044c \u043a\u0430\u043a \u0437\u0435\u043d\u0438\u0446\u0443 \u043e\u043a\u0430. \u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430, \u0442\u0430\u043a \u0438 \u0434\u043b\u044f \u0435\u0433\u043e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u0433\u043b\u0443\u0431\u0436\u0435 \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0441\u044f \u0432 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443, \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0441\u0430\u0439\u0442 <a href=\"https:\/\/jwt.io\/\" rel=\"nofollow noopener noreferrer\">jwt.io<\/a> \u2014 \u0442\u0430\u043c \u0435\u0441\u0442\u044c \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u0439 encoder \/ decoder, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b, \u0441\u0441\u044b\u043b\u043a\u0438 \u0438 \u0442.\u043f.<\/p>\n<\/div><\/div>\n<p>  <a name=\"2\"><\/a>  <\/p>\n<h2 id=\"pristupaem-k-razrabotke-opisanie-kontrakta-i-generaciya-koda\">\u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0430\u0435\u043c \u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435: \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430 \u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u0434\u0430<\/h2>\n<p>  <\/p>\n<p>\u0421 \u0447\u0435\u0433\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043b\u044e\u0431\u043e\u0433\u043e gRPC \u0441\u0435\u0440\u0432\u0438\u0441\u0430? \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u0441 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f proto-\u0444\u0430\u0439\u043b\u0430 (Protocol Buffers, protobuf). \u0415\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b \u0441 \u044d\u0442\u0438\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c, \u0442\u043e \u043d\u0435 \u043f\u0443\u0433\u0430\u0439\u0442\u0435\u0441\u044c, \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 API (\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430) \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u0411\u043b\u0438\u0436\u0430\u0439\u0448\u0430\u044f \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u044f \u0438\u0437 \u043c\u0438\u0440\u0430 REST API \u2014 Swagger \/ OpenAPI.<\/p>\n<p>  <\/p>\n<p>\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430, \u044d\u0442\u043e, \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0441\u043c\u044b\u0441\u043b\u0435, API-\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u0431\u044f\u0437\u0430\u043d\u0430 \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u044b\u0442\u044c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0439, \u0438 \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043a\u043b\u0438\u0435\u043d\u0442, \u0438 \u0441\u0435\u0440\u0432\u0435\u0440. \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0433\u0434\u0435-\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e, \u0438 \u043c\u044b \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442, \u0442.\u043a. \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0441 \u043d\u0438\u043c \u0441\u0432\u044f\u0437\u0430\u043d \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u0425\u0440\u0430\u043d\u0438\u0442\u044c proto-\u0444\u0430\u0439\u043b\u044b \u0438 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442 \u043d\u0443\u0436\u0435\u043d \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0443, \u0438 \u0432\u0441\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c (\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f SSO). \u0421\u0435\u0440\u0432\u0438\u0441\u044b \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 go.mod.<\/p>\n<p>  <\/p>\n<p>\u0418\u0442\u0430\u043a, \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c <code>protos<\/code> (\u043b\u0438\u0431\u043e <code>contract<\/code> \/ <code>api-contracts<\/code> \u0438\u043b\u0438 \u043a\u0430\u043a \u0432\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f).<\/p>\n<p>  <\/p>\n<p>\u0412 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0443 \u043c\u0435\u043d\u044f \u0431\u0443\u0434\u0435\u0442 \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u043f\u0430\u043f\u043a\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>proto<\/code> \u2014 \u0442\u0443\u0442 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0430\u043c\u0438 proto-\u0444\u0430\u0439\u043b\u044b<\/li>\n<li><code>gen<\/code> \u2014 \u0430 \u0437\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u0442 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u043e \u043d\u0438\u043c \u043a\u043e\u0434<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0435 <code>proto\/sso<\/code> \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0444\u0430\u0439\u043b <code>sso.proto<\/code>. \u0415\u0433\u043e \u0444\u043e\u0440\u043c\u0430\u0442 \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442, \u0432 \u043d\u0451\u043c \u0431\u0443\u0434\u0443\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u044b:<\/p>\n<p>  <\/p>\n<ul>\n<li><strong>\u041e\u0431\u0449\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f<\/strong>: \u0432\u0435\u0440\u0441\u0438\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430, \u043f\u0430\u043a\u0435\u0442 \u0438 \u043e\u043f\u0446\u0438\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 go-\u0444\u0430\u0439\u043b\u043e\u0432<\/li>\n<li><strong>\u0421\u0435\u0440\u0432\u0438\u0441\u044b<\/strong>: \u043f\u043e \u0441\u0443\u0442\u0438, \u044d\u0442\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u043c \u0432 Go \u2014 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440 \u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c<\/li>\n<li><strong>\u0424\u043e\u0440\u043c\u0430\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/strong>: \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443 \u043d\u0430\u0441 \u043f\u043e\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u0434\u0438\u043d: <code>Auth<\/code> (\u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u044f \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u044e \u0440\u044f\u0434\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c <code>Permissions<\/code> \u0438 <code>UserInfo<\/code>).<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">\/\/ proto\/sso\/sso.proto  \/\/ \u0412\u0435\u0440\u0441\u0438\u044f ProtoBuf syntax = \"proto3\";  \/\/ \u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u043f\u0430\u043a\u0435\u0442 - \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u043e \u0438\u043c\u0435\u043d \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u041f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0438\u0437\u0431\u0435\u0433\u0430\u0442\u044c \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u043e\u0432 \u0438\u043c\u0435\u043d. package auth;  \/\/ \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 Go \u043a\u043e\u0434\u0430. option go_package = \"tuzov.sso.v1;ssov1\";  \/\/ Auth is service for managing permissions and roles. service Auth {   \/\/ Register registers a new user.   rpc Register (RegisterRequest) returns (RegisterResponse);   \/\/ Login logs in a user and returns an auth token.   rpc Login (LoginRequest) returns (LoginResponse); }  \/\/ TODO: \u041d\u0430 \u0431\u0443\u0434\u0443\u0449\u0435\u0435, \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u0440\u044f\u043c\u043e \u0437\u0434\u0435\u0441\u044c, \/\/ \u043b\u0438\u0431\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \/\/ service Permissions { \/\/    GetUserPermissions(GetUserPermissionsRequest) return UserPermissions \/\/ }  \/\/ \u041e\u0431\u044a\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 RPC-\u043c\u0435\u0442\u043e\u0434\u0430 (\u0440\u0443\u0447\u043a\u0438) Register. message RegisterRequest {   string email = 1; \/\/ Email of the user to register.   string password = 2; \/\/ Password of the user to register. }  \/\/ \u041e\u0431\u044a\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 (\u0440\u0443\u0447\u043a\u0430) \u0432\u0435\u0440\u043d\u0451\u0442. message RegisterResponse {   int64 user_id = 1; \/\/ User ID of the registered user. }  \/\/ \u0422\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u0434\u043b\u044f \u043c\u0435\u0442\u043e\u0434\u0430 Login() message LoginRequest {   string email = 1; \/\/ Email of the user to login.   string password = 2; \/\/ Password of the user to login.   int32 app_id = 3; \/\/ ID of the app to login to. }  message LoginResponse {   string token = 1; \/\/ Auth token of the logged in user. }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e \u0433\u043e\u0442\u043e\u0432\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0443 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c Go-\u043a\u043e\u0434. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u2014 <code>protoc<\/code> (\u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 Protocol Buffers). \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430, \u0432\u0430\u043c \u0435\u0451 \u043d\u0443\u0436\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c, \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 <a href=\"https:\/\/grpc.io\/docs\/languages\/go\/quickstart\/\" rel=\"nofollow noopener noreferrer\">\u0442\u0443\u0442<\/a>. \u0412\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0438\u0442\u0430\u0439\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e \u2014 \u043d\u0443\u0436\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u0442\u0438\u043b\u0438\u0442\u0443, \u043d\u043e \u0438 \u043f\u043b\u0430\u0433\u0438\u043d \u0434\u043b\u044f Go.<\/p>\n<p>  <\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0430\u043f\u043a\u0443, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b: <\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">mkdir -p gen\/go<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">protoc -I proto proto\/sso\/sso.proto --go_out=.\/gen\/go\/ --go_opt=paths=source_relative --go-grpc_out=.\/gen\/go\/ --go-grpc_opt=paths=source_relative<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0435\u0451 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>-I proto<\/code>: \u041e\u043f\u0446\u0438\u044f <code>-I<\/code> \u0438\u043b\u0438 <code>--proto_path<\/code> \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u0443\u0442\u044c \u043a \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0441 .proto \u0444\u0430\u0439\u043b\u0430\u043c\u0438. \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0441\u043c\u043e\u0433 \u043d\u0430\u0439\u0442\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0435\u0441\u0442\u044c. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f <code>proto<\/code>.<\/li>\n<li><code>proto\/sso\/sso.proto<\/code>: \u043f\u0443\u0442\u044c \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 .proto \u0444\u0430\u0439\u043b\u0443, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u043c.<\/li>\n<li><code>--go_out=.\/gen\/go\/<\/code>: \u041e\u043f\u0446\u0438\u044f <code>--go_out<\/code> \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043a\u0443\u0434\u0430 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 Go-\u043a\u043e\u0434. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 <code>.\/gen\/go\/<\/code>.<\/li>\n<li><code>--go_opt=paths=source_relative<\/code>: \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043e\u043f\u0446\u0438\u044f \u2014 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432. <code>paths=source_relative<\/code> \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0432\u044b\u0445\u043e\u0434\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0431\u0443\u0434\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u0442\u043e\u0442 \u0436\u0435 \u043f\u0430\u043a\u0435\u0442, \u0447\u0442\u043e \u0438 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 .proto \u0444\u0430\u0439\u043b\u044b.<\/li>\n<li><code>--go-grpc_out=.\/gen\/go\/<\/code>: \u043a\u0443\u0434\u0430 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 Go gRPC-\u043a\u043e\u0434. \u041a\u0430\u043a \u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0432\u044b\u0445\u043e\u0434\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043c\u0435\u0449\u0435\u043d\u044b \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e <code>.\/gen\/go\/<\/code>.<\/li>\n<li><code>--go-grpc_opt=paths=source_relative<\/code>: \u042d\u0442\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u0430\u044f \u043e\u043f\u0446\u0438\u044f \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 Go gRPC-\u043a\u043e\u0434\u0430, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0430\u044f, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u0434\u043b\u044f gRPC.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0412\u043c\u0435\u0441\u0442\u043e \u043f\u0443\u0442\u0438 \u0434\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e proto-\u0444\u0430\u0439\u043b\u0430 \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0442 \u0442\u0430\u043a\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">protoc -I proto proto\/sso\/*.proto &lt;\u043f\u0440\u043e\u0447\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b><\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u043a\u043e\u0434 \u043f\u043e \u0432\u0441\u0435\u043c proto-\u0444\u0430\u0439\u043b\u0430\u043c \u0438\u0437 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (\u043d\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0434\u043b\u044f \u0440\u043e\u0434\u043d\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 Windows).<\/p>\n<p>  <\/p>\n<p>\u0420\u0435\u043a\u0443\u0440\u0440\u0435\u043d\u0442\u043d\u043e \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u043f\u043e\u0434\u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u043e\u043d\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0432 \u0442\u0430\u043a\u043e\u043c \u0432\u0438\u0434\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u2014 \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u041f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438, \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u043b\u0438\u0431\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0418\u0437 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043c\u044b \u0432\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u0431\u0443\u0434\u0443\u0442 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0434\u0432\u0430 \u0442\u0438\u043f\u0430 \u0444\u0430\u0439\u043b\u043e\u0432:<\/p>\n<p>  <\/p>\n<ul>\n<li><strong>Go \u043a\u043e\u0434<\/strong>: \u044d\u0442\u043e \u043d\u0430\u0431\u043e\u0440 \u0442\u0438\u043f\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043d\u0430\u0448\u0438\u043c\u0438 protobuf \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 \u0438\u0437 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u043d\u0430 Go. \u042d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c, \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c\/\u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.<\/li>\n<li><strong>Go gRPC \u043a\u043e\u0434<\/strong> \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c:<br \/> \n<ul>\n<li><strong>\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/strong>: \u044d\u0442\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 gRPC \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u043c\u0435\u0442\u043e\u0434\u044b RPC, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u044b. \u0422\u0430\u043a\u0436\u0435 \u0442\u0443\u0442 \u0431\u0443\u0434\u0443\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u0432\u043e\u0435\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u043e\u0439. \u041e\u043d\u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 &#171;\u0441\u043a\u0435\u043b\u0435\u0442&#187; \u0441\u0435\u0440\u0432\u0435\u0440\u0430.<\/li>\n<li><strong>\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/strong>: \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a gRPC \u0441\u0435\u0440\u0432\u0435\u0440\u0443.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041f\u043e\u043d\u0438\u043c\u0430\u044e, \u0447\u0442\u043e \u043f\u043e \u043d\u0430\u0447\u0430\u043b\u0443 \u0432\u0441\u0451 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043e\u0447\u0435\u043d\u044c \u0441\u043b\u043e\u0436\u043d\u043e, \u043d\u043e \u0432\u0441\u0451 \u0441\u0442\u0430\u043d\u0435\u0442 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u0435\u0439, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043d\u0430\u0447\u043d\u0435\u043c \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u0431\u0435\u0440\u0438\u0442\u0435\u0441\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0442\u0435\u0440\u043f\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u044b\u0437\u043e\u0432 \u044d\u0442\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432 Makefile, \u043b\u0438\u0431\u043e \u0432 <a href=\"https:\/\/taskfile.dev\/\" rel=\"nofollow noopener noreferrer\">Taskfile<\/a> (\u0430\u043d\u0430\u043b\u043e\u0433 Make, \u043d\u043e \u0444\u043e\u0440\u043c\u0430\u0442 \u0444\u0430\u0439\u043b\u043e\u0432 \u0431\u043e\u043b\u0435\u0435 \u0443\u0434\u043e\u0431\u043d\u044b\u0439). \u041d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0430\u0431\u043e\u0442\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c Task, \u043e\u043d \u043c\u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0438 \u044f \u043a \u043d\u0435\u043c\u0443 \u043f\u0440\u0438\u0432\u044b\u043a, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u043a\u0430\u0436\u0443 \u043d\u0430 \u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0435.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e <a href=\"https:\/\/taskfile.dev\/installation\/\" rel=\"nofollow noopener noreferrer\">\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0443\u0442\u0438\u043b\u0438\u0442\u0443 Task<\/a>.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0441 Makefile, \u0443\u0442\u0438\u043b\u0438\u0442\u0430 Task \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 Taskfile, \u043d\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0435 yaml. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0435\u0433\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\"># .\/Taskfile.yaml # See: https:\/\/taskfile.dev\/api\/    version: \"3\"    tasks:     default: # \u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443, \u0431\u0443\u0434\u0443\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u044b \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0435     cmds:         - task: generate     generate:  ## \u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438     aliases: ## \u0410\u043b\u0438\u0430\u0441\u044b \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f       - gen       desc: \"Generate code from proto files\"       cmds:  ## \u0422\u0443\u0442 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 bash-\u043a\u043e\u043c\u0430\u043d\u0434\u044b       - protoc -I proto proto\/sso\/*.proto --go_out=.\/gen\/go\/ --go_opt=paths=source_relative --go-grpc_out=.\/gen\/go\/ --go-grpc_opt=paths=source_relative<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">task generate<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 GitHub, \u0442.\u043a. \u043e\u043d \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430.<\/p>\n<p>  <a name=\"3\"><\/a>  <\/p>\n<h2 id=\"tochka-vhoda-i-konfiguraciya\">\u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f<\/h2>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u0434\u043b\u044f \u0441\u0430\u043c\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 SSO, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u043c \u0432 \u0431\u043e\u043b\u0435\u0435 \u043e\u0431\u0449\u0435\u043c \u0432\u0438\u0434\u0435. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">sso \u251c\u2500\u2500 cmd.............. \u041a\u043e\u043c\u0430\u043d\u0434\u044b \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0443\u0442\u0438\u043b\u0438\u0442 \u2502\u00a0\u00a0 \u251c\u2500\u2500 migrator.... \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u2502\u00a0\u00a0 \u2514\u2500\u2500 sso......... \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 SSO \u251c\u2500\u2500 config........... \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 yaml-\u0444\u0430\u0439\u043b\u044b \u251c\u2500\u2500 internal......... \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u2502\u00a0\u00a0 \u251c\u2500\u2500 app.......... \u041a\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 grpc.... \u0417\u0430\u043f\u0443\u0441\u043a gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u2502\u00a0\u00a0 \u251c\u2500\u2500 config....... \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u2502\u00a0\u00a0 \u251c\u2500\u2500 domain \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 models.. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u0434\u043e\u043c\u0435\u043d\u0430 \u2502\u00a0\u00a0 \u251c\u2500\u2500 grpc \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 auth.... gRPC-\u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Auth \u2502\u00a0\u00a0 \u251c\u2500\u2500 lib.......... \u041e\u0431\u0449\u0438\u0435 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u2502\u00a0\u00a0 \u251c\u2500\u2500 services..... \u0421\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 (\u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430) \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 auth \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 permissions \u2502\u00a0\u00a0 \u2514\u2500\u2500 storage...... \u0421\u043b\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438  \u2502\u00a0\u00a0     \u2514\u2500\u2500 sqlite.. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430 SQLite \u251c\u2500\u2500 migrations....... \u041c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u251c\u2500\u2500 storage.......... \u0424\u0430\u0439\u043b\u044b \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 SQLite \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u2514\u2500\u2500 tests............ \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u2014 \u0441 \u0442\u043e\u0447\u043a\u0438 \u0432\u0445\u043e\u0434\u0430. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435, \u0443 \u043d\u0430\u0441 \u0434\u043b\u044f \u043d\u0435\u0451 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0433\u043e\u0442\u043e\u0432\u043e, \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0437\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u043b\u0430\u043d \u0440\u0430\u0431\u043e\u0442, \u0438 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u0448\u0430\u0433\u043e\u0432\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0435\u0433\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ `cmd\/sso\/main.go` package main  func main() {       \/\/ TODO: \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0430      \/\/ TODO: \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u043e\u0433\u0433\u0435\u0440      \/\/ TODO: \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (app)      \/\/ TODO: \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c gRPC-\u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443, \u0442.\u0435. \u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/config\/config.go package config  type Config struct {       Env            string     `yaml:\"env\" env-default:\"local\"`       StoragePath    string     `yaml:\"storage_path\" env-required:\"true\"`       GRPC           GRPCConfig `yaml:\"grpc\"`       MigrationsPath string       TokenTTL       time.Duration `yaml:\"token_ttl\" env-default:\"1h\"`   }    type GRPCConfig struct {       Port    int           `yaml:\"port\"`       Timeout time.Duration `yaml:\"timeout\"`   }<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u0442 \u0430\u043d\u043c\u0430\u0440\u0448\u0430\u043b\u043b\u0438\u0442\u044c\u0441\u044f (\u043f\u0430\u0440\u0441\u0438\u0442\u044c\u0441\u044f) \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b. \u0422\u0430\u043a\u0436\u0435 \u043c\u044b \u0437\u0434\u0435\u0441\u044c \u0432\u0438\u0434\u0438\u043c struct-\u0442\u0435\u0433\u0438 (\u0435\u0441\u043b\u0438 \u0432\u044b \u0441 \u043d\u0438\u043c\u0438 \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b, \u0443 \u043c\u0435\u043d\u044f \u0432 \u0422\u0413-\u043a\u0430\u043d\u0430\u043b\u0435 <a href=\"https:\/\/t.me\/ntuzov\/274\" rel=\"nofollow noopener noreferrer\">\u0435\u0441\u0442\u044c \u043f\u043e\u0441\u0442 \u0441 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438<\/a>).<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0430-\u0444\u0430\u0439\u043b\u0430 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"http:\/\/github.com\/ilyakaznacheev\/cleanenv\" rel=\"nofollow noopener noreferrer\">cleanenv<\/a>, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0438 struct-\u0442\u0435\u0433\u0438 \u043c\u044b \u0437\u0434\u0435\u0441\u044c \u043f\u0438\u0448\u0435\u043c \u0434\u043b\u044f \u0434\u043b\u044f \u043d\u0435\u0451. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0439 \u043e \u043d\u0438\u0445 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438. <\/p>\n<p>  <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c <code>cleanenv<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/ilyakaznacheev\/cleanenv@v1.5.0<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u0435\u0440\u0441\u0438\u044e \u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u043b\u0438, \u043a\u0430\u043a\u0430\u044f \u0431\u044b\u043b\u0430 \u0443 \u043c\u0435\u043d\u044f, \u0438 \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043a\u043e\u0434 \u0442\u043e\u0447\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u041f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438, \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0443\u044e, \u0435\u0441\u043b\u0438 \u0447\u0438\u0442\u0430\u0435\u0442\u0435 \u0441\u0442\u0430\u0442\u044c\u044e \u0441\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u0437\u0436\u0435 \u0434\u0430\u0442\u044b \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u0412\u043a\u0440\u0430\u0442\u0446\u0435 \u043f\u043e \u043f\u0443\u043d\u043a\u0442\u0430\u043c:<\/p>\n<p>  <\/p>\n<ul>\n<li><strong>Env<\/strong> \u2014 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435: local, dev, prod \u0438 \u0442.\u043f.<\/li>\n<li><strong>StoragePath<\/strong> \u2014 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SQLite, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u0434\u043e \u0444\u0430\u0439\u043b\u0430, \u0433\u0434\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043d\u0430\u0448\u0430 \u0411\u0414<\/li>\n<li><strong>GRPCConfig<\/strong> \u2014 \u043f\u043e\u0440\u0442 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0438 \u0442\u0430\u0439\u043c\u0430\u0443\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/li>\n<li><strong>MigrationsPath<\/strong> \u2014 \u043f\u0443\u0442\u044c \u0434\u043e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0441 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f\u043c\u0438 \u0411\u0414. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0439 <code>migrator<\/code><\/li>\n<li><strong>TokenTTL<\/strong> \u2014 \u0432\u0440\u0435\u043c\u044f \u0436\u0438\u0437\u043d\u0438 \u0432\u044b\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0445 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435, \u0432\u0440\u0435\u043c\u044f \u0436\u0438\u0437\u043d\u0438 \u2014 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0448\u0442\u0443\u043a\u0430, \u0438, \u043f\u043e-\u0445\u043e\u0440\u043e\u0448\u0435\u043c\u0443, \u043e\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u043e\u0442 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0444\u0430\u043a\u0442\u043e\u0440\u043e\u0432. \u041d\u043e \u043c\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0435\u0433\u043e \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c, \u0438 \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435. \u0414\u0430\u043b\u0435\u0435 \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0433\u043e \u043f\u043e\u0434 \u0441\u0435\u0431\u044f, \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>MustLoad()<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0430\u0440\u0441\u0438\u0442\u044c \u0444\u0430\u0439\u043b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>fetchConfigPath()<\/code> \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u0443\u0442\u0438 \u0434\u043e \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/config\/config.go  func MustLoad() *Config {       configPath := fetchConfigPath()       if configPath == \"\" {           panic(\"config path is empty\")      }        \/\/ check if file exists     if _, err := os.Stat(configPath); os.IsNotExist(err) {         panic(\"config file does not exist: \" + configPath)     }      var cfg Config      if err := cleanenv.ReadConfig(configPath, &amp;cfg); err != nil {         panic(\"config path is empty: \" + err.Error())     }      return &amp;cfg }  \/\/ fetchConfigPath fetches config path from command line flag or environment variable. \/\/ Priority: flag > env > default. \/\/ Default value is empty string. func fetchConfigPath() string {     var res string      flag.StringVar(&amp;res, \"config\", \"\", \"path to config file\")     flag.Parse()      if res == \"\" {         res = os.Getenv(\"CONFIG_PATH\")     }      return res }<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p>\u041f\u043e \u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u044e, \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c <code>Must<\/code> \u0432\u043c\u0435\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043e\u0448\u0438\u0431\u043e\u043a \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u043f\u0430\u043d\u0438\u043a\u0443. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0438\u0445 \u0441 \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e.<\/p><\/blockquote>\n<p>\u0417\u0430\u0447\u0435\u043c \u043d\u0443\u0436\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>fetchConfigPath()<\/code>? \u041d\u0430\u0448\u0435\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u0433\u0434\u0435 \u0438\u0441\u043a\u0430\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c <code>configPath<\/code>. \u0421\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: \u0444\u043b\u0430\u0433 <code>--config<\/code> \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f <code>CONFIG_PATH<\/code>. \u041d\u0430\u0448\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u043e\u0431\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430. \u0415\u0441\u043b\u0438 \u0432\u0434\u0440\u0443\u0433 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u043e\u0431\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0444\u043b\u0430\u0433.<\/p>\n<p>  <\/p>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c, \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">CONFIG_PATH=.\/path\/to\/config\/file.yaml myApp<\/code><\/pre>\n<p>  <\/p>\n<p>\u041b\u0438\u0431\u043e \u0442\u0430\u043a \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0444\u043b\u0430\u0433\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">myApp --config=.\/path\/to\/config\/file.yaml<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0441\u0430\u043c \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\"># config\/config_local.yaml  env: \"local\"   storage_path: \".\/storage\/sso.db\"   grpc:     port: 44044     timeout: 10h<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>main()<\/code> \u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0442\u0430\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ cmd\/sso\/main.go  func main() {     cfg := config.MustLoad()      \/\/ ... }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0432 \u044d\u0442\u043e\u043c \u0436\u0435 \u0444\u0430\u0439\u043b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043b\u043e\u0433\u0433\u0435\u0440\u0430. \u0414\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u044b, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u0432\u044b\u0448\u0435\u0434\u0448\u0438\u0439 <code>log\/slog<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u044e\u0431\u0438\u043b \u0435\u0449\u0451 \u0434\u043e \u0435\u0433\u043e \u0440\u0435\u043b\u0438\u0437\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ cmd\/sso\/main.go  import (     \"log\/slog\"     \/\/ ... )  const (     envLocal = \"local\"     envDev   = \"dev\"     envProd  = \"prod\" )  func main() {     \/\/ ... }  func setupLogger(env string) *slog.Logger {     var log *slog.Logger      switch env {     case envLocal:         log = slog.New(             slog.NewTextHandler(os.Stdout, &amp;slog.HandlerOptions{Level: slog.LevelDebug}),         )     case envDev:         log = slog.New(             slog.NewJSONHandler(os.Stdout, &amp;slog.HandlerOptions{Level: slog.LevelDebug}),         )     case envProd:         log = slog.New(             slog.NewJSONHandler(os.Stdout, &amp;slog.HandlerOptions{Level: slog.LevelInfo}),         )     }      return log }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c, \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043b\u043e\u0433\u0433\u0435\u0440 \u0441 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li><strong>envLocal<\/strong>: \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>TextHandler<\/code>, \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0434\u043b\u044f \u043a\u043e\u043d\u0441\u043e\u043b\u0438, \u0438 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f <code>Debug<\/code> (\u0442.\u0435. \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u0432\u0441\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f)<\/li>\n<li><strong>envDev<\/strong>: \u0437\u0430\u043f\u0443\u0441\u043a \u043d\u0430 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u043c dev-\u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u2014 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u043e\u0442 \u0436\u0435, \u043d\u043e \u0444\u043e\u0440\u043c\u0430\u0442 \u0432\u044b\u0432\u043e\u0434\u0430 \u2014 <code>JSON<\/code>, \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0434\u043b\u044f \u0441\u0438\u0441\u0442\u0435\u043c \u0441\u0431\u043e\u0440\u0430 \u043b\u043e\u0433\u043e\u0432 (Kibana, Grafana Loki \u0438 \u0442.\u043f.)<\/li>\n<li><strong>envProd<\/strong>: \u0437\u0430\u043f\u0443\u0441\u043a \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435: \u043f\u043e\u0432\u044b\u0448\u0430\u0435\u043c \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043e <code>Info<\/code> \u2014 \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u044b debug-\u043b\u043e\u0433\u0438 \u0432 \u043f\u0440\u043e\u0434\u0435. \u0422.\u0435. \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u0443\u0440\u043e\u0432\u043d\u0435\u043c Info \u0438\u043b\u0438 Error.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0414\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u0432 <code>main()<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ cmd\/sso\/main.go  \/\/ ...  func main() {     cfg := config.MustLoad()      log := setupLogger(cfg.Env) }  \/\/ ...<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0442\u0438\u043f \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u2014 <code>cfg.Env<\/code>.<\/p>\n<p>  <a name=\"4\"><\/a>  <\/p>\n<h2 id=\"grpc---server-obrabotka-zaprosov\">gRPC \u2014 \u0441\u0435\u0440\u0432\u0435\u0440, \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/h2>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u0432\u0438\u043c \u043d\u0435\u043d\u0430\u0434\u043e\u043b\u0433\u043e \u043d\u0430\u0448 <code>main.go<\/code> \u0438 \u0441\u043f\u0443\u0441\u0442\u0438\u043c\u0441\u044f \u043d\u0430 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043d\u0438\u0436\u0435 \u2014 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u0417\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0439 <code>protoc<\/code> Go-\u0444\u0430\u0439\u043b\u044b, \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <code>protos<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0440\u0430\u043d\u0435\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u044d\u0442\u043e\u0442 \u043f\u0430\u043a\u0435\u0442, \u0432 \u043c\u043e\u0451\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a (\u0432\u044b, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0435 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442):<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/GolangLessons\/protos<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u043c \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/grpc\/auth\/server.go  import (     \"context\"      \"google.golang.org\/grpc\"      \/\/ \u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u0434     ssov1 \"github.com\/GolangLessons\/protos\/gen\/go\/sso\" )  type serverAPI struct {     ssov1.UnimplementedAuthServer \/\/ \u0425\u0438\u0442\u0440\u0430\u044f \u0448\u0442\u0443\u043a\u0430, \u043e \u043d\u0435\u0439 \u043d\u0438\u0436\u0435     auth Auth }  \/\/ \u0422\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u043a\u043e\u0442\u0440\u044b\u0439 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u043b\u0438 \u0432 grpcApp type Auth interface {     Login(         ctx context.Context,         email string,         password string,         appID int,     ) (token string, err error)     RegisterNewUser(         ctx context.Context,         email string,         password string,     ) (userID int64, err error) }  func Register(gRPCServer *grpc.Server, auth Auth) {       ssov1.RegisterAuthServer(gRPCServer, &amp;serverAPI{auth: auth})   }  func (s *serverAPI) Login(     ctx context.Context,     in *ssov1.LoginRequest, ) (*ssov1.LoginResponse, error) {     \/\/ TODO }  func (s *serverAPI) Register(     ctx context.Context,     in *ssov1.RegisterRequest, ) (*ssov1.RegisterResponse, error) {     \/\/ TODO } <\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043e\u043f\u0438\u0441\u0430\u043b\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 <code>serverAPI<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b API<\/li>\n<li>\u043a\u0430\u0440\u043a\u0430\u0441\u044b \u0434\u043b\u044f \u0434\u0432\u0443\u0445 RPC-\u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c: <code>Login<\/code> \u0438 <code>Register<\/code> (\u043c\u0435\u0442\u043e\u0434\u044b \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b <code>serverAPI<\/code>)<\/li>\n<li>\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e <code>Auth<\/code> \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u2014 \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435, \u0430 \u043f\u043e\u043a\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430<\/li>\n<li>\u0442\u0430\u043a\u0436\u0435 \u0437\u0434\u0435\u0441\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>Register<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u044d\u0442\u0443 <code>serverAPI<\/code> \u0432 gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u0435<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0412 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435 <code>serverAPI<\/code> \u0443 \u043d\u0430\u0441 \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 <code>UnimplementedAuthServer<\/code> \u2014 <code>protoc<\/code> \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0435\u0451 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 proto-\u0444\u0430\u0439\u043b\u0430. \u041e\u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0435\u043a\u0443\u044e \u043f\u0443\u0441\u0442\u0443\u044e \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u0432\u0441\u0435\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 gRPC \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u043e\u0431\u0440\u0430\u0442\u043d\u0443\u044e \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 <code>.proto<\/code> \u0444\u0430\u0439\u043b\u0430. \u0415\u0441\u043b\u0438 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432 \u043d\u0430\u0448 <code>.proto<\/code> \u0444\u0430\u0439\u043b \u0438 \u0437\u0430\u043d\u043e\u0432\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u0434, \u043d\u043e \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0432 <code>serverAPI<\/code>, \u0442\u043e \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0432\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u043d\u0438\u044e <code>UnimplementedAuthServer<\/code> \u043d\u0430\u0448 \u043a\u043e\u0434 \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u0430 \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0435\u0440\u043d\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0443 &#171;Not implemented&#187;.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 (<code>ssov1.RegisterAuthServer<\/code>) \u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \/ \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0437\u0430 \u043d\u0430\u0441 \u0442\u0430\u043a\u0436\u0435 \u0443\u0436\u0435 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b, \u0447\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e. \u042d\u0442\u043e \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u043a\u0443\u0447\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b (\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438) \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/grpc\/auth\/server.go  import (     \"context\"      ssov1 \"github.com\/GolangLessons\/protos\/gen\/go\/sso\"     \"google.golang.org\/grpc\/codes\"     \"google.golang.org\/grpc\/status\" )  type Auth interface {     \/\/ .. \u0441\u043c. \u0432\u044b\u0448\u0435 }  func (s *serverAPI) Login(     ctx context.Context,     in *ssov1.LoginRequest, ) (*ssov1.LoginResponse, error) {     if in.Email == \"\" {         return nil, status.Error(codes.InvalidArgument, \"email is required\")     }      if in.Password == \"\" {         return nil, status.Error(codes.InvalidArgument, \"password is required\")     }      if in.GetAppId() == 0 {         return nil, status.Error(codes.InvalidArgument, \"app_id is required\")     }      token, err := s.auth.Login(ctx, in.GetEmail(), in.GetPassword(), int(in.GetAppId()))     if err != nil {         \/\/ \u041e\u0448\u0438\u0431\u043a\u0443 auth.ErrInvalidCredentials \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0438\u0436\u0435         if errors.Is(err, auth.ErrInvalidCredentials) {             return nil, status.Error(codes.InvalidArgument, \"invalid email or password\")         }          return nil, status.Error(codes.Internal, \"failed to login\")     }      return &amp;ssov1.LoginResponse{Token: token}, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438. \u041b\u0438\u0431\u043e \u0434\u0430\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u043f\u0430\u043a\u0435\u0442 \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438. \u042f \u0436\u0435 \u043e\u0441\u0442\u0430\u0432\u043b\u044e \u043a\u0430\u043a \u0435\u0441\u0442\u044c.<\/p>\n<p>  <\/p>\n<blockquote><p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>status.Error<\/code> \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 <code>grpc\/status<\/code>. \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0444\u043e\u0440\u043c\u0430\u0442 \u043e\u0448\u0438\u0431\u043a\u0438 \u0431\u044b\u043b \u043f\u043e\u043d\u044f\u0442\u0435\u043d \u043b\u044e\u0431\u043e\u043c\u0443 grpc-\u043a\u043b\u0438\u0435\u043d\u0442\u0443.<\/p><\/blockquote>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043c\u044b \u043f\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u0435\u043c \u044d\u0442\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435 \u043a\u043e\u0434 \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 <code>grpc\/codes<\/code> \u2014 \u044d\u0442\u043e \u0442\u043e\u0436\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043b\u044f \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438 \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c\u0438. \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u043a\u043e\u0434\u044b \u043e\u0448\u0438\u0431\u043e\u043a \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u043f\u043e\u0434\u043e\u0448\u0435\u043b \u043f\u0430\u0440\u043e\u043b\u044c \u0438\u043b\u0438 \u043c\u044b \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0411\u0414, \u044d\u0442\u043e \u2014 <code>codes.InvalidArgument<\/code>, \u0435\u0441\u043b\u0438 \u0436\u0435 \u0411\u0414 \u0432\u0435\u0440\u043d\u0443\u043b\u0430 \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443, \u044d\u0442\u043e \u0443\u0436\u0435 <code>codes.Internal<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0412\u0441\u0451 \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u043d\u0430\u0448\u0438\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u0431\u043e\u043b\u0435\u0435 \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u0435\u0442\u043e\u0434 <code>Register()<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/grpc\/auth\/server.go  func (s *serverAPI) Register(     ctx context.Context,     in *ssov1.RegisterRequest, ) (*ssov1.RegisterResponse, error) {     if in.Email == \"\" {         return nil, status.Error(codes.InvalidArgument, \"email is required\")     }      if in.Password == \"\" {         return nil, status.Error(codes.InvalidArgument, \"password is required\")     }      uid, err := s.auth.RegisterNewUser(ctx, in.GetEmail(), in.GetPassword())     if err != nil {         \/\/ \u041e\u0448\u0438\u0431\u043a\u0443 storage.ErrUserExists \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0438\u0436\u0435         if errors.Is(err, storage.ErrUserExists) {             return nil, status.Error(codes.AlreadyExists, \"user already exists\")         }          return nil, status.Error(codes.Internal, \"failed to register user\")     }      return &amp;ssov1.RegisterResponse{UserId: uid}, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u043c, \u043d\u0430\u0448\u0438 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u044b\u0435, \u0438 \u043d\u0430 \u044d\u0442\u043e\u043c \u043e\u043d\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0433\u043e\u0442\u043e\u0432\u044b. \u0412\u0441\u044f \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u043c \u0441\u043b\u043e\u0435, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u043d\u0435\u043c\u0443.<\/p>\n<p>  <a name=\"5\"><\/a>  <\/p>\n<h2 id=\"servisnyy-sloy-auth\">\u0421\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u0441\u043b\u043e\u0439. Auth<\/h2>\n<p>  <\/p>\n<p>\u041d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c \u0438\u0445, \u0432\u044b\u0434\u0430\u0432\u0430\u044f \u0442\u043e\u043a\u0435\u043d\u044b. \u041e\u0447\u0435\u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0435\u043c\u0443 \u043d\u0443\u0436\u0435\u043d \u043d\u0435\u043a\u0438\u0439 <code>Storage<\/code> (\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u0445 \u2014 \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0443\u0436\u0435 \u0441\u043b\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u041a \u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u044b \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0435, \u0430 \u043f\u043e\u043a\u0430 \u043d\u0430\u043c, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u043e\u0431\u043c\u0435\u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043c\u0435\u0436\u0434\u0443 \u044d\u0442\u0438\u043c\u0438 \u0441\u043b\u043e\u044f\u043c\u0438 \u043e\u043f\u0438\u0448\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/domain\/models\/user.go package models  type User struct {     ID       int64     Email    string     PassHash []byte } <\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0449\u0430\u044e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u0430\u043f\u043a\u0430 <code>models<\/code> \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 <code>internal\/domain<\/code> \u2014 \u0442\u0443\u0442 \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u043e\u0431\u0449\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0434\u043b\u044f \u0432\u0441\u0435\u0433\u043e \u0434\u043e\u043c\u0435\u043d\u0430, \u0430 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f. \u0422\u043e \u0435\u0441\u0442\u044c, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e \u0438\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u043b\u043e\u044f\u043c\u0438.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/services\/auth\/auth.go  type UserStorage interface {       SaveUser(ctx context.Context, email string, passHash []byte) (uid int64, err error)       User(ctx context.Context, email string) (models.User, error)   }<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442, \u0438 \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0435 \u044f \u0447\u0430\u0449\u0435 \u0432\u0438\u0436\u0443 \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434. \u041d\u043e \u043b\u0438\u0447\u043d\u043e \u043c\u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0438\u0434\u0435\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0438 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0431\u0443\u0434\u0443 \u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/services\/auth\/auth.go  type UserSaver interface {     SaveUser(         ctx context.Context,         email string,         passHash []byte,     ) (uid int64, err error) }  type UserProvider interface {       User(ctx context.Context, email string) (models.User, error)   }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c, \u0443 \u043d\u0430\u0441 \u0442\u0443\u0442 \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0432\u043e\u044f \u0443\u0437\u043a\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043c\u043e\u0441\u0442\u0438. \u042d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u043d\u0430\u0448 \u043a\u043e\u0434 \u0431\u043e\u043b\u0435\u0435 \u0433\u0438\u0431\u043a\u0438\u043c, \u0432\u0435\u0434\u044c \u043a\u0442\u043e \u0441\u043a\u0430\u0437\u0430\u043b, \u0447\u0442\u043e \u0437\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043e\u0431\u044f\u0437\u0430\u043d\u0430 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u043e\u0434\u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430? \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043c\u044b \u0437\u0430\u0445\u043e\u0442\u0438\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u0444\u043a\u0443, \u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0432\u043e\u043e\u0431\u0449\u0435 gRPC \/ HTTP \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c? \u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0447\u0442\u043e \u044d\u0442\u043e \u0440\u0435\u0434\u043a\u0438\u0435 \u043a\u0435\u0439\u0441\u044b, \u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u0442\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434 \u0432\u0441\u0451 \u043f\u043e\u0434\u0440\u044f\u0434, \u043d\u043e \u0432\u0435\u0434\u044c \u0438 \u0446\u0435\u043d\u0430 \u0437\u0430 \u044d\u0442\u043e \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0443\u043b\u0435\u0432\u0430\u044f.<\/p>\n<p>  <\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u043f\u0440\u043e\u0449\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u0412\u0441\u044f\u043a\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0439, \u0447\u0435\u043c \u043d\u0435\u043a\u0438\u0439 \u043e\u0431\u0449\u0438\u0439 <code>UserStorage<\/code>, \u0441\u043e\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u0438\u0437 50 \u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e \u0432\u0441\u0435\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0446\u0435\u043b\u0438\u043a\u043e\u043c, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043b\u0438\u0448\u044c \u043e\u0434\u0438\u043d \u043c\u0435\u0442\u043e\u0434.<\/p>\n<p>  <\/p>\n<p>\u0412\u044b \u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0438\u0437 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445, \u043a\u0430\u043a\u043e\u0439 \u0432\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e \u043f\u043e\u0432\u043e\u0434\u0443 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u2014 \u0434\u0430, \u044f \u043e\u0431\u044b\u0447\u043d\u043e \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u044e \u0438\u0445 \u0432 \u043c\u0435\u0441\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f, \u0430 \u043d\u0435 \u0440\u044f\u0434\u043e\u043c \u0441 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u041f\u043e\u0447\u0435\u043c\u0443 \u0442\u0430\u043a, \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u043b \u0432 <a href=\"https:\/\/youtu.be\/eYHCCht8eX4?si=EW5XsOCuw2qgoUjZ\" rel=\"nofollow noopener noreferrer\">\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u0440\u043e\u043b\u0438\u043a\u0435<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u0432\u0438\u0433\u0430\u0435\u043c\u0441\u044f \u0434\u0430\u043b\u044c\u0448\u0435. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c\u0441\u044f \u043d\u0435 \u0432\u043e \u0432\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441\u0440\u0430\u0437\u0443, \u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043e\u0434\u043d\u043e \u043a\u0430\u043a\u043e\u0435-\u0442\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0435, \u0438 \u0435\u0433\u043e JWT-\u0442\u043e\u043a\u0435\u043d \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u043b\u044e\u0447\u043e\u043c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043e\u043d \u043b\u043e\u0433\u0438\u043d\u0438\u0442\u0441\u044f \u0432 URL Shortener. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043f\u043e\u043c\u0438\u043c\u043e \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438, \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 (App). \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430, \u043d\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e ID \u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430. \u0417\u0430\u0432\u0435\u0434\u0451\u043c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0434\u0435\u043b\u044c <code>App<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/domain\/models\/app.go  package models  type App struct {     ID     int     Name   string     Secret string } <\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f <code>App<\/code> \u0438\u0437 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/services\/auth\/auth.go \/\/ ... type AppProvider interface {       App(ctx context.Context, appID int) (models.App, error)   }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Auth:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/services\/auth\/auth.go  \/\/ ...  type Auth struct {     log         *slog.Logger     usrSaver    UserSaver     usrProvider UserProvider     appProvider AppProvider     tokenTTL    time.Duration }  func New(       log *slog.Logger,       userSaver UserSaver,       userProvider UserProvider,       appProvider AppProvider,       tokenTTL time.Duration,   ) *Auth {       return &amp;Auth{          usrSaver:    userSaver,          usrProvider: userProvider,          log:         log,          appProvider: appProvider,          tokenTTL:    tokenTTL,  \/\/ \u0412\u0440\u0435\u043c\u044f \u0436\u0438\u0437\u043d\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0445 \u0442\u043e\u043a\u0435\u043d\u043e\u0432     }   }<\/code><\/pre>\n<p>  <\/p>\n<blockquote><p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u2014 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c <code>userSaver<\/code>, <code>userProvider<\/code> \u0438 <code>appProvider<\/code> \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438, \u0445\u043e\u0442\u044f \u0443 \u043d\u0438\u0445 \u0443 \u0432\u0441\u0435\u0445 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0449\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f. \u0422.\u0435. \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u0442\u0440\u0438 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430.<\/p><\/blockquote>\n<p>\u041a\u043e\u043c\u0443-\u0442\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u043c, \u0438 \u0434\u043e\u043b\u044f \u0438\u0441\u0442\u0438\u043d\u044b \u0432 \u044d\u0442\u043e\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0435\u0441\u0442\u044c. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0432\u0442\u043e\u0440\u044e\u0441\u044c \u2014 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u044d\u0442\u0438 \u0442\u0440\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0432 \u043e\u0434\u0438\u043d, \u044d\u0442\u043e \u043d\u0435 \u043f\u043e\u043c\u0435\u0448\u0430\u0435\u0442 \u0432\u0430\u043c \u043f\u0440\u043e\u0439\u0442\u0438 \u0433\u0430\u0439\u0434 \u0434\u043e \u043a\u043e\u043d\u0446\u0430. \u041b\u0438\u0431\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043e\u0433\u043b\u0430\u0441\u0438\u0442\u044c\u0441\u044f \u0441 \u043c\u043e\u0438\u043c\u0438 \u0434\u043e\u0432\u043e\u0434\u0430\u043c\u0438 \u0432\u044b\u0448\u0435 \u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u0418 \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u043c\u043e\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043c \u0442\u0443\u0442 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f. \u0412 \u043f\u0430\u043a\u0435\u0442\u0435 <code>log\/slog<\/code> \u043c\u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0448\u0438\u0431\u043a\u0438 \u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043b\u043e\u0433\u0430. \u0418 \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u043e, \u044f \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0438\u0448\u0443 \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0443\u044e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/logger\/sl\/sl.go  package sl  import (     \"log\/slog\" )  func Err(err error) slog.Attr {     return slog.Attr{         Key:   \"error\",         Value: slog.StringValue(err.Error()),     } } <\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0442\u0435\u043f\u0435\u0440\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0442 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">if err != nil {     a.log.Error(\"failed to get user\", sl.Err(err)) }<\/code><\/pre>\n<p>  <\/p>\n<h3 id=\"metod-registernewuser\">\u041c\u0435\u0442\u043e\u0434 RegisterNewUser<\/h3>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043a \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0435 \u2014 \u043a \u043c\u0435\u0442\u043e\u0434\u0430\u043c <code>RegisterNewUser<\/code> \u0438 <code>Login<\/code>. \u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u043f\u0435\u0440\u0432\u043e\u0433\u043e. \u0414\u043b\u044f \u043d\u0435\u0433\u043e \u043d\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u043f\u0430\u043a\u0435\u0442 <code>crypto\/bcrypt<\/code> \u2014 \u0447\u0443\u0442\u044c \u043d\u0438\u0436\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u0438 \u0437\u0430\u0447\u0435\u043c, \u0430 \u043f\u043e\u043a\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0435\u0451 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get golang.org\/x\/crypto@v0.13.0<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/services\/auth\/auth.go  import (     \/\/ ...     \"golang.org\/x\/crypto\/bcrypt\"      \"grpc-service-ref\/internal\/lib\/logger\/sl\" )  \/\/ RegisterNewUser registers new user in the system and returns user ID. \/\/ If user with given username already exists, returns error. func (a *Auth) RegisterNewUser(ctx context.Context, email string, pass string) (int64, error) {     \/\/ op (operation) - \u0438\u043c\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438 \u043f\u0430\u043a\u0435\u0442\u0430. \u0422\u0430\u043a\u0443\u044e \u043c\u0435\u0442\u043a\u0443 \u0443\u0434\u043e\u0431\u043d\u043e     \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0432 \u043b\u043e\u0433\u0438 \u0438 \u0432 \u0442\u0435\u043a\u0441\u0442 \u043e\u0448\u0438\u0431\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u043b\u0435\u0433\u0447\u0435 \u0431\u044b\u043b\u043e \u0438\u0441\u043a\u0430\u0442\u044c \u0445\u0432\u043e\u0441\u0442\u044b     \/\/ \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u043e\u043b\u043e\u043c\u043e\u043a.     const op = \"Auth.RegisterNewUser\"      \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u0441 \u0434\u043e\u043f. \u043f\u043e\u043b\u044f\u043c\u0438, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u043c\u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u0443\u044e \u0438\u043d\u0444\u0443     \/\/ \u043e \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0432\u044b\u0437\u043e\u0432\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438     log := a.log.With(         slog.String(\"op\", op),         slog.String(\"email\", email),     )      log.Info(\"registering user\")      \/\/ \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0445\u044d\u0448 \u0438 \u0441\u043e\u043b\u044c \u0434\u043b\u044f \u043f\u0430\u0440\u043e\u043b\u044f.     passHash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)     if err != nil {         log.Error(\"failed to generate password hash\", sl.Err(err))          return 0, fmt.Errorf(\"%s: %w\", op, err)     }      \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0411\u0414     id, err := a.usrSaver.SaveUser(ctx, email, passHash)     if err != nil {         log.Error(\"failed to save user\", sl.Err(err))          return 0, fmt.Errorf(\"%s: %w\", op, err)     }      return id, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>bcrypt.GenerateFromPassword()<\/code>, \u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u043e\u0432 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u0435\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u043f\u0440\u043e\u0432\u0435\u0434\u0443 \u043e\u0447\u0435\u043d\u044c \u043a\u0440\u0430\u0442\u043a\u0438\u0439 \u043b\u0438\u043a\u0431\u0435\u0437 \u043f\u043e \u044d\u0442\u043e\u0439 \u0442\u0435\u043c\u0435. \u041a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0439\u0442\u0435, \u0447\u0442\u043e \u044d\u0442\u043e \u043b\u0438\u0448\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430, \u0438 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0438\u0449\u0438\u0442\u0435 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435. \u0415\u0441\u043b\u0438 \u0436\u0435 \u0432\u0430\u043c \u044d\u0442\u043e \u0437\u043d\u0430\u043a\u043e\u043c\u043e, \u0442\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043c\u0435\u043b\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043b\u0438\u043a\u0431\u0435\u0437.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041b\u0438\u043a\u0431\u0435\u0437 \u043f\u043e \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044e \u043f\u0430\u0440\u043e\u043b\u0435\u0439<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<p>\u0412 \u0447\u0438\u0441\u0442\u043e\u043c \u0432\u0438\u0434\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0435\u043b\u044c\u0437\u044f \u2014 \u044d\u0442\u043e \u0447\u0440\u0435\u0432\u0430\u0442\u043e \u0443\u0442\u0435\u0447\u043a\u0430\u043c\u0438: \u0435\u0441\u043b\u0438 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a \u0443\u043a\u0440\u0430\u0434\u0451\u0442 \u043d\u0430\u0448\u0443 \u0411\u0414, \u043e\u043d \u0443\u0437\u043d\u0430\u0435\u0442 \u043f\u0430\u0440\u043e\u043b\u0438 \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0438\u0445 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430\u043c \u0438 \u0442.\u043f. \u041e\u0447\u0435\u043d\u044c \u043f\u043b\u043e\u0445\u043e, \u043e\u0447\u0435\u043d\u044c \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0431\u0440\u0430\u0442\u044c \u0445\u044d\u0448 \u043e\u0442 \u043f\u0430\u0440\u043e\u043b\u044f \u0438 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0443\u0436\u0435 \u0435\u0433\u043e (\u0435\u0441\u043b\u0438 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442\u0435 \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0435 \u0445\u044d\u0448, \u0443 \u043c\u0435\u043d\u044f <a href=\"https:\/\/youtu.be\/0UX4MIfOMEs?si=JNybTzWNoWzV78Wt\" rel=\"nofollow noopener noreferrer\">\u0435\u0441\u0442\u044c \u043e\u0431 \u044d\u0442\u043e\u043c \u0432\u0438\u0434\u0435\u043e<\/a>), \u0430 \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c\u0441\u044f, \u043c\u044b \u0441\u043d\u043e\u0432\u0430 \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0445\u044d\u0448 \u043e\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0430\u0440\u043e\u043b\u044f \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u043c \u0445\u044d\u0448\u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f \u0434\u0432\u0443\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439.<\/p>\n<p>  <\/p>\n<p>\u041d\u043e \u044d\u0442\u043e \u0442\u043e\u0436\u0435 \u043e\u043f\u0430\u0441\u043d\u043e, \u0442.\u043a. \u0435\u0441\u043b\u0438 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a \u043f\u043e\u0434\u0431\u0435\u0440\u0451\u0442 \u0438\u043b\u0438 \u0443\u043a\u0440\u0430\u0434\u0451\u0442 \u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u0438\u043d \u043f\u0430\u0440\u043e\u043b\u044c, \u0442\u043e \u043e\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u043a\u043e \u0432\u0441\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0441 \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043f\u0430\u0440\u043e\u043b\u0435\u043c (\u0442.\u043a. \u0443 \u043d\u0438\u0445 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u043e\u0439 \u0436\u0435 \u0445\u044d\u0448). \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Rainbow_table\" rel=\"nofollow noopener noreferrer\">\u0435\u0441\u0442\u044c \u0441\u043f\u043e\u0441\u043e\u0431\u044b<\/a> \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u043e \u0445\u044d\u0448\u0443 \u043f\u0430\u0440\u043e\u043b\u044c, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0435\u0441\u043b\u0438 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.<\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u0436\u0435 \u0434\u0435\u043b\u0430\u0442\u044c? \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u2014 \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 (\u0435\u0451 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u2014 \u0441\u043e\u043b\u044c, salt), \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0451 \u043a \u043f\u0430\u0440\u043e\u043b\u044e: <code>&lt;password>+&lt;salt><\/code>, \u0431\u0435\u0440\u0451\u043c \u0445\u044d\u0448 \u043e\u0442 \u0442\u043e\u0433\u043e \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0432 \u0411\u0414 \u0438 \u0440\u044f\u0434\u043e\u043c \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u0438\u0434\u0435 \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u043e\u043b\u044c. \u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c\u0441\u044f, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043b\u044c \u043a \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u043c\u044b\u043c \u043f\u0430\u0440\u043e\u043b\u044f\u043c \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0442\u044c \u0445\u044d\u0448\u0438. \u0422\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0435\u0439, \u043f\u0430\u0440\u043e\u043b\u044c \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0438 \u0441\u043b\u0438\u0432 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u0430\u0440\u043e\u043b\u044f \u043d\u0435 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0434\u043e\u0431\u0440\u0430\u0442\u044c \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435.<\/p>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u0432\u044b \u043b\u0435\u0433\u043a\u043e \u043d\u0430\u0439\u0434\u0451\u0442\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043d\u0451\u043c \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435.<\/p>\n<\/div><\/div>\n<p>  <\/p>\n<h4 id=\"kak-rabotaet-funkciya-bcryptgeneratefrompassword\">\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>bcrypt.GenerateFromPassword<\/code><\/h4>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f <code>bcrypt.GenerateFromPassword()<\/code> \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u043f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0443 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0445\u044d\u0448\u0430 \u0438 \u0441\u043e\u043b\u0438 \u0437\u0430 \u043d\u0430\u0441. \u041e\u043d\u0430 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043d\u0430 \u0432\u0445\u043e\u0434 \u043f\u0430\u0440\u043e\u043b\u044c, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u043b\u044c, \u0445\u044d\u0448\u0438\u0440\u0443\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0435\u0434\u0438\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0438 \u0445\u044d\u0448, \u0438 \u0441\u043e\u043b\u044c, \u0447\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e \u2014 \u0432\u0435\u0434\u044c \u043d\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435.<\/p>\n<p>  <\/p>\n<p>\u0423 \u044d\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0435\u0441\u0442\u044c \u0432\u0442\u043e\u0440\u043e\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u2014 <code>bcrypt.DefaultCost<\/code>. \u0427\u0435\u043c \u0432\u044b\u0448\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430, \u0442\u0435\u043c \u043b\u0443\u0447\u0448\u0435 \u0437\u0430\u0449\u0438\u0449\u0435\u043d \u043f\u0430\u0440\u043e\u043b\u044c, \u043d\u043e \u0442\u0435\u043c \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0435\u0433\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043b\u0443\u0447\u0448\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c <code>DefaultCost<\/code>, \u0430 \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0441\u0435\u0440\u044c\u0435\u0437\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0441\u0442\u043e\u0438\u0442 \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u0443 \u0442\u0435\u043c\u0443 \u0433\u043b\u0443\u0431\u0436\u0435.<\/p>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>bcrypt.CompareHashAndPassword(savedHash, loginPassword)<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442 \u0437\u0430 \u043d\u0430\u0441 \u0432\u0441\u044e \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0438 \u0441\u043e\u043e\u0431\u0449\u0438\u0442 \u2014 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u043f\u0430\u0440\u043e\u043b\u044c \u0438\u043b\u0438 \u043d\u0435\u0442.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c \u043a\u0430\u043a \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043a \u043c\u0435\u0442\u043e\u0434\u0443 <code>Login<\/code>, \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 JWT-\u0442\u043e\u043a\u0435\u043d\u043e\u0432, \u0442.\u043a. \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u2014 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<h3 id=\"generaciya-jwt-tokena-dlya-avtorizacii\">\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f JWT-\u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 JWT \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/golang-jwt\/jwt\/v5@v5.0.0<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u043e\u043a\u0435\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u043e \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438. \u041c\u043e\u0436\u043d\u043e \u0432\u0441\u0435 \u044d\u0442\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u043d\u044b\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043b\u0438\u0431\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0440\u0430\u0437\u0443 \u043c\u043e\u0434\u0435\u043b\u0438 <code>User<\/code> \u0438 <code>App<\/code>. \u041c\u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442:<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430. \u042f \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b \u0435\u0451 \u0432 \u043f\u0430\u043a\u0435\u0442\u0435 <code>internal\/lib\/jwt<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/lib\/jwt\/jwt.go  \/\/ NewToken creates new JWT token for given user and app. func NewToken(user models.User, app models.App, duration time.Duration) (string, error) {       token := jwt.New(jwt.SigningMethodHS256)        \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0442\u043e\u043a\u0435\u043d \u0432\u0441\u044e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e     claims := token.Claims.(jwt.MapClaims)       claims[\"uid\"] = user.ID       claims[\"email\"] = user.Email       claims[\"exp\"] = time.Now().Add(duration).Unix()       claims[\"app_id\"] = app.ID        \/\/ \u041f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f     tokenString, err := token.SignedString([]byte(app.Secret))       if err != nil {          return \"\", err       }        return tokenString, nil   }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u044d\u0442\u0443 \u0441\u0442\u0440\u043e\u0447\u043a\u0443: <code>claims[\"exp\"] = time.Now().Add(duration).Unix()<\/code>. \u0412 \u043d\u0435\u0439 \u043c\u044b \u0437\u0430\u0434\u0430\u0451\u043c \u0441\u0440\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (TTL) \u0442\u043e\u043a\u0435\u043d\u0430 \u0432 \u0432\u0438\u0434\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043c\u0435\u0442\u043a\u0438, \u0434\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u0434\u043b\u0430\u0439\u043d\u0430 \u0442\u043e\u043a\u0435\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f &#171;\u043f\u0440\u043e\u0442\u0443\u0445\u0448\u0438\u043c&#187;, \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043c\u044b \u0435\u0433\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c.<\/p>\n<p>  <\/p>\n<p><code>NewToken<\/code> \u2014 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u043d\u043e \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0432\u0430\u043c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u043b\u044f \u043d\u0435\u0451 \u0442\u0435\u0441\u0442\u044b \u2014 \u044d\u0442\u043e \u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u0430\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430, \u0438 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432\u0430\u043c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c, \u0435\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442\u0435 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442.<\/p>\n<p>  <\/p>\n<h3 id=\"metod-login\">\u041c\u0435\u0442\u043e\u0434 <code>Login<\/code><\/h3>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434 <code>Login<\/code>.<\/p>\n<p>  <\/p>\n<blockquote><p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435! \u0422\u0435\u043a\u0443\u0449\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u0435\u0442\u043e\u0434\u0430 \u0438\u043c\u0435\u0435\u0442 \u043e\u0434\u043d\u0443 \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u0443\u044e \u0434\u044b\u0440\u0443 \u0432 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u2014 \u043e\u043d \u043d\u0435 \u0437\u0430\u0449\u0438\u0449\u0435\u043d \u043e\u0442 \u0431\u0440\u0443\u0442\u0444\u043e\u0440\u0441\u0430 (\u043f\u0435\u0440\u0435\u0431\u043e\u0440\u0430 \u043f\u0430\u0440\u043e\u043b\u0435\u0439). \u042f \u0440\u0435\u0448\u0438\u043b \u043d\u0435 \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0443 \u0440\u0430\u0437\u0431\u043e\u0440\u043e\u043c \u044d\u0442\u043e\u0439 \u0442\u0435\u043c\u044b. \u0421\u043e\u0432\u0435\u0442\u0443\u044e \u0432\u0430\u043c \u0431\u044b\u0442\u044c \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u0435\u0439 \u2014 \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u0442\u044c \u043a\u0430\u043a\u0443\u044e-\u0442\u043e \u043b\u043e\u0433\u0438\u043a\u0443 \u0434\u043b\u044f \u0437\u0430\u0449\u0438\u0442\u044b. \u041b\u0438\u0431\u043e \u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445, \u0447\u0442\u043e \u0432\u0430\u043c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430 \u044d\u0442\u0430 \u0442\u0435\u043c\u0430, \u044f \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043e\u0431 \u044d\u0442\u043e\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u043c\u0435\u0442\u043a\u0443 \u0432 \u0422\u0413-\u043a\u0430\u043d\u0430\u043b\u0435, \u043b\u0438\u0431\u043e \u0432 \u0432\u0438\u0434\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430.<\/p><\/blockquote>\n<p>\u041e\u043d \u0437\u0430\u043c\u0435\u0442\u043d\u043e \u0434\u043b\u0438\u043d\u043d\u0435\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e, \u043d\u043e \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043a\u0430\u0436\u0443 \u0435\u0433\u043e \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u0438 \u043f\u0440\u043e\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u044e:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/services\/auth\/auth.go  var (     ErrInvalidCredentials = errors.New(\"invalid credentials\") )  \/\/ Login checks if user with given credentials exists in the system and returns access token. \/\/ \/\/ If user exists, but password is incorrect, returns error. \/\/ If user doesn't exist, returns error. func (a *Auth) Login(     ctx context.Context,     email string,     password string, \/\/ \u043f\u0430\u0440\u043e\u043b\u044c \u0432 \u0447\u0438\u0441\u0442\u043e\u043c \u0432\u0438\u0434\u0435, \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u0435\u0439 \u0441 \u043b\u043e\u0433\u0430\u043c\u0438!     appID int, \/\/ ID \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043b\u043e\u0433\u0438\u043d\u0438\u0442\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c ) (string, error) {     const op = \"Auth.Login\"      log := a.log.With(         slog.String(\"op\", op),         slog.String(\"username\", email),         \/\/ password \u043b\u0438\u0431\u043e \u043d\u0435 \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u043c, \u043b\u0438\u0431\u043e \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0432 \u0437\u0430\u043c\u0430\u0441\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435     )      log.Info(\"attempting to login user\")      \/\/ \u0414\u043e\u0441\u0442\u0430\u0451\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0411\u0414     user, err := a.usrProvider.User(ctx, email)     if err != nil {         if errors.Is(err, storage.ErrUserNotFound) {             a.log.Warn(\"user not found\", sl.Err(err))              return \"\", fmt.Errorf(\"%s: %w\", op, ErrInvalidCredentials)         }          a.log.Error(\"failed to get user\", sl.Err(err))          return \"\", fmt.Errorf(\"%s: %w\", op, err)     }      \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0430\u0440\u043e\u043b\u044f     if err := bcrypt.CompareHashAndPassword(user.PassHash, []byte(password)); err != nil {         a.log.Info(\"invalid credentials\", sl.Err(err))          return \"\", fmt.Errorf(\"%s: %w\", op, ErrInvalidCredentials)     }      \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438     app, err := a.appProvider.App(ctx, appID)     if err != nil {         return \"\", fmt.Errorf(\"%s: %w\", op, err)     }      log.Info(\"user logged in successfully\")      \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0442\u043e\u043a\u0435\u043d \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438     token, err := jwt.NewToken(user, app, a.tokenTTL)     if err != nil {         a.log.Error(\"failed to generate token\", sl.Err(err))          return \"\", fmt.Errorf(\"%s: %w\", op, err)     }      return token, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441 <code>Auth<\/code> \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0433\u043e\u0442\u043e\u0432, \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430.<\/p>\n<p>  <a name=\"6\"><\/a>  <\/p>\n<h2 id=\"sloy-raboty-s-dannymi---storage\">\u0421\u043b\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u2014 Storage<\/h2>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/www.sqlite.org\/index.html\" rel=\"nofollow noopener noreferrer\">SQLite<\/a> \u2014 \u043e\u0447\u0435\u043d\u044c \u043b\u044e\u0431\u043b\u044e \u0435\u0451 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432, \u0442.\u043a. \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0432\u043e\u0437\u0438\u0442\u044c\u0441\u044f \u0441 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439, \u0432\u0441\u044f \u0411\u0414 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u043e\u0434\u043d\u043e\u043c \u0444\u0430\u0439\u043b\u0435, \u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043d\u0435\u0439 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u0434\u0440\u0430\u0439\u0432\u0435\u0440\u0430 sqlite \u0432 \u0432\u0438\u0434\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0447\u0435\u0440\u0435\u0437 go mod:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/mattn\/go-sqlite3@v1.14.17<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043d \u043d\u0430\u043c \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435.<\/p>\n<p>  <\/p>\n<p>\u041a\u043e\u0434 \u043f\u0430\u043a\u0435\u0442\u0430 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u044f \u0440\u0430\u0437\u043c\u0435\u0449\u0443 \u0432 <code>internal\/storage<\/code>, \u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u043b\u044f sqlite \u043f\u043e\u043b\u043e\u0436\u0443 \u0441\u044e\u0434\u0430: <code>internal\/storage\/sqlite<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0412 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u043f\u0430\u043a\u0435\u0442\u0435 <code>storage<\/code> \u044f \u0445\u0440\u0430\u043d\u044e \u043b\u0438\u0448\u044c \u043e\u0431\u0449\u0438\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0438\u043f\u043e\u0432, \u043e\u0448\u0438\u0431\u043e\u043a \u0438 \u0442.\u043f. \u0412 \u043c\u043e\u0451\u043c \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 \u044d\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0448\u0438\u0431\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/storage.go package storage    import \"errors\"    var (       ErrUserExists   = errors.New(\"user already exists\")       ErrUserNotFound = errors.New(\"user not found\")       ErrAppNotFound  = errors.New(\"app not found\")   )<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e \u044d\u0442\u0438\u043c \u043e\u0448\u0438\u0431\u043a\u0430\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u0441\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043d\u044f\u0442\u044c, \u0447\u0442\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a, \u0438 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f. \u041e\u043d\u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 (\u0431\u0443\u0434\u044c \u0442\u043e SQLite, Postgres, MongoDB \u0438 \u0442.\u043f.), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u0438\u0445 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b\u0438 \u0432 \u043e\u0431\u0449\u0435\u043c \u043f\u0430\u043a\u0435\u0442\u0435.<\/p>\n<p>  <\/p>\n<h3 id=\"migracii-i-shema-dannyh\">\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0438 \u0441\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0441\u0445\u0435\u043c\u0443 \u0434\u0430\u043d\u043d\u044b\u0445. \u041f\u043e \u043f\u0440\u043e\u0441\u044c\u0431\u0430\u043c \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \/ \u0437\u0440\u0438\u0442\u0435\u043b\u0435\u0439 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \/ \u0440\u043e\u043b\u0438\u043a\u0430 \u043f\u043e REST API, \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043e \u0441\u0445\u0435\u043c\u043e\u0439 \u0411\u0414.<\/p>\n<p>  <\/p>\n<p>\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0411\u0414 \u2014 \u044d\u0442\u043e \u043f\u043e\u0448\u0430\u0433\u043e\u0432\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u0438 \u043e\u0442\u043a\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u0445\u0435\u043c\u044b. \u041e\u043d\u0438, \u043e\u0431\u044b\u0447\u043d\u043e, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0438\u0437 \u0441\u0435\u0431\u044f \u043d\u0430\u0431\u043e\u0440 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e \u043e\u0447\u0435\u0440\u0435\u0434\u0438, \u0432\u044b \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442\u0435 \u0441\u0445\u0435\u043c\u0443 \u0411\u0414 \u043a \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e. \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u043d\u043e\u0432\u0438\u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u043a\u043e\u0434\u043e\u043c, \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0442\u0430\u043a \u0436\u0435 \u043b\u0435\u0433\u043a\u043e \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0411\u0414.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0435 \u043e\u0441\u043e\u0431\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e \u044d\u0442\u0430 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f, \u044d\u0442\u043e \u043d\u0435 \u0441\u0442\u0440\u0430\u0448\u043d\u043e, \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0441\u0442\u0430\u043d\u0435\u0442 \u044f\u0441\u043d\u0435\u0435. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438. \u041c\u044b, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/golang-migrate\/migrate\/v4@v4.16.2<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043c\u0438\u0433\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u043d\u0430 Go \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e, \u0438 \u0432\u0441\u0435 \u043e\u043d\u0438 \u043f\u043b\u044e\u0441-\u043c\u0438\u043d\u0443\u0441 \u043d\u0435\u043f\u043b\u043e\u0445\u0438\u0435, \u0442\u0430\u043a \u0447\u0442\u043e \u0432\u044b\u0431\u043e\u0440 \u043d\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u0435\u043d.<\/p>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043c\u0438\u0433\u0440\u0430\u0442\u043e\u0440\u043e\u043c? \u0415\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0432 Docker-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435 (\u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442)<\/li>\n<li>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0433\u043e<\/li>\n<li>\u041d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443-\u0432\u0440\u0430\u043f\u043f\u0435\u0440<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041c\u043d\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442, \u0442.\u043a. \u0435\u0433\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u041d\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 \u043d\u0438\u0445 \u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u044d\u0442\u043e \u043d\u0435 \u0441\u043b\u043e\u0436\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0444\u0430\u0439\u043b <code>cmd\/migrator\/main.go<\/code> \u0438 \u043f\u0438\u0448\u0435\u043c \u0432 \u043d\u0451\u043c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0443\u044e \u043e\u0431\u0451\u0440\u0442\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ cmd\/migrator\/main.go  package main  import (     \"flag\"     \"fmt\"      \/\/ \u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439     \"github.com\/golang-migrate\/migrate\/v4\"     \/\/ \u0414\u0440\u0430\u0439\u0432\u0435\u0440 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 SQLite 3     _ \"github.com\/golang-migrate\/migrate\/v4\/database\/sqlite3\"     \/\/ \u0414\u0440\u0430\u0439\u0432\u0435\u0440 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432     _ \"github.com\/golang-migrate\/migrate\/v4\/source\/file\" )  func main() {     var storagePath, migrationsPath, migrationsTable string      \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u0444\u043b\u0430\u0433\u043e\u0432 \u0437\u0430\u043f\u0443\u0441\u043a\u0430      \/\/ \u041f\u0443\u0442\u044c \u0434\u043e \u0444\u0430\u0439\u043b\u0430 \u0411\u0414.     \/\/ \u0415\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e, \u0442.\u043a. \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c SQLite, \u0434\u0440\u0443\u0433\u0438\u0435 \u043a\u0440\u0435\u0434\u044b \u043d\u0435 \u043d\u0443\u0436\u043d\u044b.     flag.StringVar(&amp;storagePath, \"storage-path\", \"\", \"path to storage\")     \/\/ \u041f\u0443\u0442\u044c \u0434\u043e \u043f\u0430\u043f\u043a\u0438 \u0441 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f\u043c\u0438.     flag.StringVar(&amp;migrationsPath, \"migrations-path\", \"\", \"path to migrations\")     \/\/ \u0422\u0430\u0431\u043b\u0438\u0446\u0430, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f\u0445. \u041e\u043d\u0430 \u043d\u0443\u0436\u043d\u0430      \/\/ \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0443\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u044b, \u0430 \u043a\u0430\u043a\u0438\u0435 \u043d\u0435\u0442.     \/\/ \u0414\u0435\u0444\u043e\u043b\u0442\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 - 'migrations'.     flag.StringVar(&amp;migrationsTable, \"migrations-table\", \"migrations\", \"name of migrations table\")     flag.Parse() \/\/ \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u0444\u043b\u0430\u0433\u043e\u0432      \/\/ \u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432     if storagePath == \"\" {         \/\/ \u041f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0438\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u0448\u0438\u0431\u043a\u0438 :)         \/\/ \u041f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438, \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442.         \/\/ \u041c\u0435\u043d\u044f \u043f\u0430\u043d\u0438\u043a\u0430 \u043f\u043e\u043a\u0430 \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u044d\u0442\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0443\u0442\u0438\u043b\u0438\u0442\u0430.         panic(\"storage-path is required\")     }     if migrationsPath == \"\" {         panic(\"migrations-path is required\")     }      \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043c\u0438\u0433\u0440\u0430\u0442\u043e\u0440\u0430, \u043f\u0435\u0440\u0435\u0434\u0430\u0432 \u043a\u0440\u0435\u0434\u044b \u043d\u0430\u0448\u0435\u0439 \u0411\u0414     m, err := migrate.New(         \"file:\/\/\"+migrationsPath,         fmt.Sprintf(\"sqlite3:\/\/%s?x-migrations-table=%s\", storagePath, migrationsTable),     )     if err != nil {         panic(err)     }      \/\/ \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438     if err := m.Up(); err != nil {         if errors.Is(err, migrate.ErrNoChange) {             fmt.Println(\"no migrations to apply\")              return         }          panic(err)     } } <\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u043c\u044b \u0437\u0434\u0435\u0441\u044c \u0432\u044b\u043d\u0435\u0441\u043b\u0438 \u043d\u0435\u0439\u043c\u0438\u043d\u0433 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u043b\u0430\u0433 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 <code>?x-migrations-table=%s<\/code>. \u041e\u0431\u044b\u0447\u043d\u043e \u044d\u0442\u043e \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043d\u043e \u044f \u0431\u0443\u0434\u0443 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432, \u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043d\u0438\u0445 \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435. \u041d\u043e \u043e\u0431 \u044d\u0442\u043e\u043c \u043f\u043e\u0437\u0436\u0435 \u2014 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0440\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.<\/p>\n<p>  <\/p>\n<p>\u0423 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u043d\u0430\u043c\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u043d\u0435\u0439\u043c\u0438\u043d\u0433\u0430 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439: <code>&lt;number>_&lt;title>.&lt;direction>.sql<\/code>, \u0433\u0434\u0435:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>number<\/code> \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0440\u044f\u0434\u043a\u0430 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u043e\u043d\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u043e \u0432\u043e\u0437\u0440\u0430\u0441\u0442\u0430\u043d\u0438\u044e \u043d\u043e\u043c\u0435\u0440\u043e\u0432. \u0422\u0443\u0442 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0446\u0435\u043b\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u2014 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u0440\u044f\u0434\u043a\u043e\u0432\u044b\u0439 \u043d\u043e\u043c\u0435\u0440, timestamp \u0438 \u0442.\u043f. \u042f \u0431\u0443\u0434\u0443 \u0438\u043c\u0435\u043d\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443: 1, 2, 3 \u0438 \u0442.\u0434.<\/li>\n<li><code>title<\/code> \u2014 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439, \u043e\u043d \u043d\u0443\u0436\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043b\u044e\u0434\u0435\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0449\u0435 \u0431\u044b\u043b\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439<\/li>\n<li><code>direction<\/code> \u2014 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>up<\/code> \u0438\u043b\u0438 <code>down<\/code>. \u0424\u0430\u0439\u043b\u044b \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c <code>up<\/code> \u0432 \u0438\u043c\u0435\u043d\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442 \u0441\u0445\u0435\u043c\u0443 \u0434\u043e \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438, <code>down<\/code> \u2014 \u043e\u0442\u043a\u0430\u0442\u044b\u0432\u0430\u044e\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043f\u0435\u0440\u0432\u0443\u044e \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u043f\u0430\u043f\u043a\u0435 <code>.\/migrations<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"sql\">-- migrations\/1_init.up.sql  CREATE TABLE IF NOT EXISTS users (     id           INTEGER PRIMARY KEY,     email        TEXT    NOT NULL UNIQUE,     pass_hash    BLOB    NOT NULL ); CREATE INDEX IF NOT EXISTS idx_email ON users (email);  CREATE TABLE IF NOT EXISTS apps (     id     INTEGER PRIMARY KEY,     name   TEXT NOT NULL UNIQUE,     secret TEXT NOT NULL UNIQUE );<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u0430 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u0438\u043d\u0434\u0435\u043a\u0441\u044b. \u0422\u0430\u0431\u043b\u0438\u0446\u044b \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0435, \u0438 \u0432\u044b\u0448\u0435 \u043c\u044b \u0443\u0436\u0435 \u043e\u0431\u0441\u0443\u0436\u0434\u0430\u043b\u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0445, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0443\u0442 \u0432\u0441\u0451 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043f\u043e\u043d\u044f\u0442\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b <code>email<\/code>, <code>name<\/code> \u0438 <code>secret<\/code> \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0438 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 \u0434\u043b\u044f \u043d\u0438\u0445 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 <a href=\"https:\/\/www.tutorialspoint.com\/sqlite\/sqlite_constraints.htm\" rel=\"nofollow noopener noreferrer\">constraint<\/a>.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u043d\u0430\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"sql\">-- .\/migrations\/1_init.down.sql  DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS apps;<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0430\u0432\u0435\u0434\u0451\u043c \u043f\u0430\u043f\u043a\u0443 \u043f\u043e\u0434 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u0411\u0414:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">mkdir storage<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0443\u0442\u0438\u043b\u0438\u0442\u0443, \u0443\u043a\u0430\u0437\u0430\u0432 \u043f\u0443\u0442\u044c \u0434\u043e \u0444\u0430\u0439\u043b\u0430 \u0411\u0414 \u0438 \u043f\u0430\u043f\u043a\u0438 \u0441 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f\u043c\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go run .\/cmd\/migrator --storage-path=.\/storage\/sso.db --migrations-path=.\/migrations<\/code><\/pre>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0441\u0451 \u043f\u0440\u043e\u0448\u043b\u043e \u0445\u043e\u0440\u043e\u0448\u043e, \u0443 \u0432\u0430\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u0444\u0430\u0439\u043b \u0411\u0414 (<code>.\/storage\/sso.db<\/code>) \u0441 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0445\u0435\u043c\u043e\u0439.<\/p>\n<p>  <\/p>\n<h3 id=\"realizaciya-storage-dlya-sqlite-3\">\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f Storage \u0434\u043b\u044f SQLite 3<\/h3>\n<p>  <\/p>\n<p>\u042d\u0442\u043e\u0442 \u0440\u0430\u0437\u0434\u0435\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u043e, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u0444\u043e\u043a\u0443\u0441 \u043d\u0430 gRPC, \u0430 \u043d\u0435 \u043d\u0430 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438 \u0441 \u0411\u0414. \u0415\u0441\u043b\u0438 \u0432\u044b \u0437\u043d\u0430\u043a\u043e\u043c\u044b \u0441 \u043f\u0430\u043a\u0435\u0442\u043e\u043c <code>database\/sql<\/code>, \u0442\u043e \u0432\u0430\u043c \u0432\u0441\u0451 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043d\u044f\u0442\u043d\u043e. \u0415\u0441\u043b\u0438 \u043d\u0435\u0442, \u0442\u043e \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f, \u043e\u043d \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0439 \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c\u044e \u0440\u0430\u043d\u043e \u0438\u043b\u0438 \u043f\u043e\u0437\u0434\u043d\u043e \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u0430\u043c \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0435.<\/p>\n<p>  <\/p>\n<p>\u0412 \u043b\u044e\u0431\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043a\u0430 \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438, \u043a\u043e\u043d\u0446\u0435\u0442\u0440\u0438\u0440\u0443\u044f\u0441\u044c \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0442\u0435\u043c\u0435, \u043d\u043e \u043a\u043e\u0433\u0434\u0430 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u0435, \u0443 \u0432\u0430\u0441 \u0443\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0442\u0435\u043c\u0430 \u0434\u043b\u044f \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435, \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0441 SQLite \u0432 Go, \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c <a href=\"https:\/\/youtu.be\/rrDtiKKO_mc\" rel=\"nofollow noopener noreferrer\">\u043c\u043e\u0439 \u0440\u043e\u043b\u0438\u043a \u043d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0412\u044b\u0448\u0435 \u043c\u044b \u0443\u0436\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0434\u0440\u0430\u0439\u0432\u0435\u0440 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 SQLite. \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043b\u0438, \u043d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u044e:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/mattn\/go-sqlite3@v1.14.16<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0444\u0430\u0439\u043b, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u043f\u0438\u0448\u0435\u043c \u0442\u0438\u043f <code>Storage<\/code> \u0438 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u0434\u043b\u044f \u043d\u0435\u0433\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/sqlite\/sqlite.go  import (     \"fmt\"      \"github.com\/mattn\/go-sqlite3\" )  type Storage struct {     db *sql.DB }  \/\/ \u041a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 Storage func New(storagePath string) (*Storage, error) {     const op = \"storage.sqlite.New\"      \/\/ \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u0434\u043e \u0444\u0430\u0439\u043b\u0430 \u0411\u0414     db, err := sql.Open(\"sqlite3\", storagePath)     if err != nil {         return nil, fmt.Errorf(\"%s: %w\", op, err)     }      return &amp;Storage{db: db}, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0440\u0438 \u043c\u0435\u0442\u043e\u0434\u0430: <code>SaveUser()<\/code>, <code>User()<\/code>, <code>App()<\/code>, \u043d\u0430\u0447\u043d\u0451\u043c \u043f\u0435\u0440\u0432\u043e\u0433\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/sqlite\/sqlite.go  import (     \"context\"     \"database\/sql\"     \"errors\"     \"fmt\"      \"grpc-service-ref\/internal\/storage\"      \"github.com\/mattn\/go-sqlite3\" )  \/\/ SaveUser saves user to db. func (s *Storage) SaveUser(ctx context.Context, email string, passHash []byte) (int64, error) {     const op = \"storage.sqlite.SaveUser\"      \/\/ \u041f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0437\u043f\u0440\u043e\u0441 \u043d\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     stmt, err := s.db.Prepare(\"INSERT INTO users(email, pass_hash) VALUES(?, ?)\")     if err != nil {         return 0, fmt.Errorf(\"%s: %w\", op, err)     }      \/\/ \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441, \u043f\u0435\u0440\u0435\u0434\u0430\u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b     res, err := stmt.ExecContext(ctx, email, passHash)     if err != nil {         var sqliteErr sqlite3.Error          \/\/ \u041d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u0443\u043d\u0433-\u0444\u0443 \u0434\u043b\u044f \u0432\u044b\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0448\u0438\u0431\u043a\u0438 ErrConstraintUnique         \/\/ (\u0441\u043c. \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u043d\u0438\u0436\u0435)         if errors.As(err, &amp;sqliteErr) &amp;&amp; sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {             return 0, fmt.Errorf(\"%s: %w\", op, storage.ErrUserExists)         }          return 0, fmt.Errorf(\"%s: %w\", op, err)     }      \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c ID \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438     id, err := res.LastInsertId()     if err != nil {         return 0, fmt.Errorf(\"%s: %w\", op, err)     }      return id, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u043e\u0442 \u044d\u0442\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043e\u0448\u0438\u0431\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">if errors.As(err, &amp;sqliteErr) &amp;&amp; sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {         return 0, fmt.Errorf(\"%s: %w\", op, storage.ErrUserExists) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u0443\u0442\u044c \u044d\u0442\u043e\u0439 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u044f\u0432\u0438\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443 \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0441\u0442\u0440\u044d\u0438\u043d\u0442\u0430 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043f\u043e <code>email<\/code>, \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438 \u2014 \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0437\u0430\u043f\u0438\u0441\u044c \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c <code>email<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435. \u0415\u0441\u043b\u0438 \u043c\u044b \u0435\u0451 \u0432\u044b\u044f\u0432\u043b\u044f\u0435\u043c, \u0442\u043e \u043d\u0430\u0440\u0443\u0436\u0443 \u043d\u0443\u0436\u043d\u043e \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443 <code>storage.ErrUserExists<\/code>, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438 \u0437\u0430\u0440\u0430\u043d\u0435\u0435.<\/p>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432\u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0439 \u0411\u0414 \u0432\u0441\u0435\u0433\u0434\u0430 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u0430 \u0438\u043c\u0435\u044e\u0449\u0435\u0433\u043e\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041d\u0430\u043c \u044d\u0442\u043e \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043b\u043e\u044f\u0445, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c.<\/p>\n<p>  <\/p>\n<p>\u0418\u0434\u0451\u043c \u0434\u0430\u043b\u044c\u0448\u0435, \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434 <code>User(ctx context.Context, email string)<\/code> \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e <code>email<\/code>: <\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/sqlite\/sqlite.go  import (     \"context\"     \"database\/sql\"     \"errors\"     \"fmt\"      \"grpc-service-ref\/internal\/domain\/models\"     \"grpc-service-ref\/internal\/storage\" )  \/\/ User returns user by email. func (s *Storage) User(ctx context.Context, email string) (models.User, error) {     const op = \"storage.sqlite.User\"      stmt, err := s.db.Prepare(\"SELECT id, email, pass_hash FROM users WHERE email = ?\")     if err != nil {         return models.User{}, fmt.Errorf(\"%s: %w\", op, err)     }      row := stmt.QueryRowContext(ctx, email)      var user models.User     err = row.Scan(&amp;user.ID, &amp;user.Email, &amp;user.PassHash)     if err != nil {         if errors.Is(err, sql.ErrNoRows) {             return models.User{}, fmt.Errorf(\"%s: %w\", op, storage.ErrUserNotFound)         }          return models.User{}, fmt.Errorf(\"%s: %w\", op, err)     }      return user, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443, \u043d\u043e \u043d\u0430 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 <code>sql.ErrNoRows<\/code>, \u043e\u043d\u0430 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0447\u0442\u043e \u043c\u044b \u043d\u0435 \u0441\u043c\u043e\u0433\u043b\u0438 \u043d\u0430\u0439\u0442\u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c. \u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u044b \u0432\u0435\u0440\u043d\u0451\u043c \u043d\u0430\u0440\u0443\u0436\u0443 <code>storage.ErrUserNotFound<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u0441\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043c\u0435\u0442\u043e\u0434 \u2014 <code>App(ctx context.Context, id int)<\/code><\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ App returns app by id. func (s *Storage) App(ctx context.Context, id int) (models.App, error) {     const op = \"storage.sqlite.App\"      stmt, err := s.db.Prepare(\"SELECT id, name, secret FROM apps WHERE id = ?\")     if err != nil {         return models.App{}, fmt.Errorf(\"%s: %w\", op, err)     }      row := stmt.QueryRowContext(ctx, id)      var app models.App     err = row.Scan(&amp;app.ID, &amp;app.Name, &amp;app.Secret)     if err != nil {         if errors.Is(err, sql.ErrNoRows) {             return models.App{}, fmt.Errorf(\"%s: %w\", op, storage.ErrAppNotFound)         }          return models.App{}, fmt.Errorf(\"%s: %w\", op, err)     }      return app, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0437\u0430\u043f\u0438\u0441\u0438 (<code>sql.ErrNoRows<\/code>), \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043d\u0430\u0440\u0443\u0436\u0443 <code>storage.ErrAppNotFound<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043d\u0430\u0448 <code>Storage<\/code> \u0433\u043e\u0442\u043e\u0432, \u0438 \u043c\u044b \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0451 \u044d\u0442\u043e \u0434\u0435\u043b\u043e \u0432\u043e\u0435\u0434\u0438\u043d\u043e.<\/p>\n<p>  <a name=\"7\"><\/a>  <\/p>\n<h2 id=\"sobiraem-komponenty-prilozheniya-voedino\">\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432\u043e\u0435\u0434\u0438\u043d\u043e<\/h2>\n<p>  <\/p>\n<p>\u0418\u0442\u0430\u043a, \u043c\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 Auth (\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u0441\u043b\u043e\u0439) \u0438 Storage (\u0441\u043b\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438), \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0445 \u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e.<\/p>\n<p>  <\/p>\n<p>\u0421\u0430\u043c\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0430\u043a\u0435\u0442 <code>internal\/app<\/code>. \u041d\u0435\u0442, \u043d\u0435 <code>cmd\/sso\/main.go<\/code> \u2014 \u044d\u0442\u043e \u0431\u044b\u043b\u0430 \u043b\u0438\u0448\u044c \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0434\u0435\u043b\u0430\u0435\u0442 \u043a\u043e\u0434 <code>main<\/code> \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0449\u0435, \u0438, \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u0434\u0430\u0451\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u043c\u0435\u0441\u0442\u0430\u0445 \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u0442\u0435\u0441\u0442\u0430\u0445, \u0447\u0442\u043e \u043e\u0431\u043b\u0435\u0433\u0447\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c, gRPC-\u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u044b \u0437\u0430\u0432\u0435\u0440\u043d\u0451\u043c \u0432 \u0435\u0449\u0451 \u043e\u0434\u043d\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (<code>internal\/app\/grpc<\/code>) \u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441 \u044d\u0442\u043e\u0433\u043e \u0438 \u043d\u0430\u0447\u043d\u0451\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/app\/grpc\/app.go  package grpcapp  import (     \"context\"     \"log\/slog\"      \"google.golang.org\/grpc\" )  type App struct {     log        *slog.Logger     gRPCServer *grpc.Server     port       int \/\/ \u041f\u043e\u0440\u0442, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c grpc-\u0441\u0435\u0440\u0432\u0435\u0440 }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043e\u043f\u0438\u0441\u0430\u043b\u0438 \u0442\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u2014 \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e Auth, \u043d\u043e \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435.<\/p>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440. \u0412 \u043d\u0451\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <code>grpc-ecosystem\/go-grpc-middleware<\/code>, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0443\u044e \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u043e\u0432 (\u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e \u043d\u0438\u0445 \u0431\u0443\u0434\u0435\u0442 \u043d\u0438\u0436\u0435). \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0435\u0451 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/grpc-ecosystem\/go-grpc-middleware\/v2@v2.0.0<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440. \u041a\u043e\u0434\u0430 \u0432 \u043d\u0451\u043c \u043d\u0435 \u043c\u043d\u043e\u0433\u043e, \u043d\u043e \u0435\u0441\u0442\u044c \u043d\u0435\u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u044d\u0442\u0430\u043f\u043d\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/app\/grpc\/app.go \/\/ ...  \/\/ New creates new gRPC server app. func New(log *slog.Logger, authService authgrpc.Auth, port int) *App {     \/\/ TODO: \u0441\u043e\u0437\u0434\u0430\u0442\u044c gRPCServer \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043a \u043d\u0435\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u044b      \/\/ TODO: \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0430\u0448 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441 Auth      \/\/ TODO: \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 App \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u043c\u0438 \u043f\u043e\u043b\u044f\u043c\u0438 }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0434\u0438\u043d \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 <code>authgrpc.Auth<\/code> \u2014 \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0439, \u043d\u0435 \u043f\u0443\u0442\u0430\u0442\u044c gRPC-\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c Auth. \u0415\u0433\u043e \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0447\u0443\u0442\u044c \u043d\u0438\u0436\u0435.<\/p>\n<p>  <\/p>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">gRPCServer := grpc.NewServer(opts)<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430 \u0432\u0445\u043e\u0434 \u043e\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u043f\u0446\u0438\u0438, \u0438 \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e \u0431\u0443\u0434\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u044b (<a href=\"https:\/\/grpc.io\/blog\/grpc-web-interceptor\/\" rel=\"nofollow noopener noreferrer\">Interceptors<\/a>). <\/p>\n<p>  <\/p>\n<p>\u0418\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440 gRPC \u044d\u0442\u043e, \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0441\u043c\u044b\u0441\u043b\u0435, \u0430\u043d\u0430\u043b\u043e\u0433 Middleware \u0438\u0437 \u043c\u0438\u0440\u0430 HTTP \/ REST \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432. \u0422\u043e \u0435\u0441\u0442\u044c, \u044d\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0434 \u0438\/\u0438\u043b\u0438 \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 RPC-\u0432\u044b\u0437\u043e\u0432\u0430 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430. \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u043e\u0432 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u0434\u0440.), \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 RPC.<\/p>\n<p>  <\/p>\n<p>\u0414\u043e\u043f\u0438\u0448\u0435\u043c \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/app\/grpc\/app.go package grpcapp  import (     \/\/ ...     \"github.com\/grpc-ecosystem\/go-grpc-middleware\/v2\/interceptors\/recovery\" )  \/\/ ...  \/\/ New creates new gRPC server app. func New(log *slog.Logger, authService AuthService, port int) *App {     \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0441 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u043e\u043c     gRPCServer := grpc.NewServer(grpc.ChainUnaryInterceptor(         recovery.UnaryServerInterceptor(),     ))      \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0448 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441 Auth, \u043e\u0431 \u044d\u0442\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043d\u0438\u0436\u0435     authgrpc.Register(gRPCServer, authService)      return &amp;App{         log:        log,         gRPCServer: gRPCServer,         port:       port,     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0423 \u043d\u0430\u0441 \u043f\u043e\u043a\u0430 \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u0438\u043d \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440, \u0438 \u044f \u0435\u0433\u043e \u043e\u0431\u0435\u0440\u043d\u0443\u043b <code>grpc.ChainUnaryInterceptor<\/code> \u2014 \u044d\u0442\u043e \u043d\u0435\u043a\u0438\u0439 \u0432\u0440\u0430\u043f\u043f\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043d\u0430\u0431\u043e\u0440 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u043e\u0432, \u0430 \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 (<code>Unary<\/code>), \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0432\u0441\u0435 \u044d\u0442\u0438 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u044b \u043f\u043e\u043e\u0447\u0435\u0440\u0451\u0434\u043d\u043e (\u043e\u0431 \u044d\u0442\u043e\u043c \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u0441\u043b\u043e\u0432\u043e <code>Chain<\/code> \u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0438).<\/p>\n<p>  <\/p>\n<blockquote><p>\u041f\u043e\u043c\u0438\u043c\u043e \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, gRPC \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0442\u0430\u043a\u0436\u0435 \u0441 \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u044b\u043c\u0438 (<code>Stream<\/code>), \u0438 \u0434\u043b\u044f \u043d\u0438\u0445 \u043c\u044b \u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438 <code>grpc.ChainStreamInterceptor<\/code>, \u043d\u043e \u044d\u0442\u043e \u0443\u0436\u0435 \u0434\u0440\u0443\u0433\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u0441\u043e \u0441\u0442\u0440\u0438\u043c\u0430\u043c\u0438 \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0435\u043c.<\/p><\/blockquote>\n<p>\u0418\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440 <code>recovery.UnaryServerInterceptor<\/code> \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0430\u043d\u0438\u043a\u0443, \u0435\u0441\u043b\u0438 \u043e\u043d\u0430 \u0441\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430. \u041f\u043e\u043b\u0435\u0437\u043d\u0430\u044f \u0448\u0442\u0443\u043a\u0430, \u0432\u0435\u0434\u044c \u043c\u044b \u043d\u0435 \u0445\u043e\u0442\u0438\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u0430\u043d\u0438\u043a\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0443\u0440\u043e\u043d\u0438\u043b\u0430 \u043d\u0430\u043c \u0432\u0435\u0441\u044c \u0441\u0435\u0440\u0432\u0438\u0441, \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0434\u0430\u0436\u0435 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u0412\u043e\u043e\u0431\u0449\u0435, \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0430\u043d\u0438\u043a\u0438, \u044d\u0442\u043e \u043f\u043e\u0440\u043e\u0439 \u0434\u0438\u0441\u043a\u0443\u0442\u0438\u0432\u043d\u0430\u044f \u0442\u0435\u043c\u0430, \u0438 \u0435\u0441\u043b\u0438 \u0432\u0430\u043c \u0442\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f, \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u044d\u0442\u043e\u0442 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440.<\/p>\n<p>  <\/p>\n<p>\u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0432\u0438\u0434\u0435 \u043f\u0430\u043d\u0438\u043a\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u043c\u043e\u043b\u0447\u0430 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f, \u0438 \u0435\u0441\u043b\u0438 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0441\u0432\u043e\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0439 <code>RecoveryHandler<\/code>. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0445\u044d\u043d\u0434\u043b\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0430\u043d\u0438\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u044d\u0442\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">recoveryOpts := []recovery.Option{     recovery.WithRecoveryHandler(func(p interface{}) (err error) {         \/\/ \u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u0430\u043d\u0438\u043a\u0435 \u0441 \u0443\u0440\u043e\u0432\u043d\u0435\u043c Error         log.Error(\"Recovered from panic\", slog.Any(\"panic\", p))          \/\/ \u041c\u043e\u0436\u0435\u0442\u0435 \u043b\u0438\u0431\u043e \u0447\u0435\u0441\u0442\u043d\u043e \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0430\u043d\u0438\u043a\u0438         \/\/ \u041b\u0438\u0431\u043e \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c - \"internal error\", \u0435\u0441\u043b\u0438 \u043d\u0435 \u0445\u043e\u0442\u0438\u043c \u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u043e\u0441\u0442\u044f\u043c\u0438         return status.Errorf(codes.Internal, \"internal error\")     }), }  gRPCServer := grpc.NewServer(grpc.ChainUnaryInterceptor(     recovery.UnaryServerInterceptor(recoveryOpts...), ))<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443, \u0438\u0437 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0443 \u043d\u0430\u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u043b\u043e\u0433\u0438, \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u041d\u043e \u044f \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0432\u0430\u043c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0438 \u0430\u043b\u0435\u0440\u0442\u044b, \u0438 \u0432 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0435 \u043f\u0430\u043d\u0438\u043a\u0438 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043c\u0435\u0442\u0440\u0438\u043a\u0443 \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0430\u043b\u0435\u0440\u0442, \u044d\u0442\u043e \u0432\u0430\u0436\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0432\u0430\u0436\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440 \u2014 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0438 \u043e\u0442\u0432\u0435\u0442\u044b. \u042d\u0442\u043e \u0431\u044b\u0432\u0430\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0435 \u0445\u0432\u043e\u0441\u0442\u043e\u0432 \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u043e\u043b\u043e\u043c\u043e\u043a \u0438 \u0434\u0435\u0431\u0430\u0433\u0430.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430: <code>logging.UnaryServerInterceptor(log, opts)<\/code> (\u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u0441\u043c. \u043d\u0438\u0436\u0435). \u041d\u0430 \u0432\u0445\u043e\u0434 \u043e\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043b\u043e\u0433\u0433\u0435\u0440 \u0438 \u043e\u043f\u0446\u0438\u0438. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043d\u0430\u0448 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043b\u043e\u0433\u0433\u0435\u0440, \u0442.\u043a. \u0443 \u0435\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 <code>Log()<\/code> \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0430, \u043e\u0442 \u0442\u043e\u0439 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0445\u044d\u043d\u0434\u043b\u0435\u0440:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ have  Log(context.Context, slog.Level, string, ...any) \/\/ need Log(context.Context, logging.Level, string, ...any)<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043d\u0430\u043c \u0441\u043d\u043e\u0432\u0430 \u043d\u0443\u0436\u0435\u043d \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0432\u0440\u0430\u043f\u043f\u0435\u0440: <\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ InterceptorLogger adapts slog logger to interceptor logger. \/\/ This code is simple enough to be copied and not imported. func InterceptorLogger(l *slog.Logger) logging.Logger {     return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) {         l.Log(ctx, slog.Level(lvl), msg, fields...)     }) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0438\u043c\u0435\u044e\u0449\u0443\u044e\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>Log()<\/code> \u0432 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u0443\u044e \u0438\u0437 \u043f\u0430\u043a\u0435\u0442\u0430 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u0430.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u043b\u043e\u0433\u0433\u0435\u0440\u0430, \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043e\u043f\u0446\u0438\u0438. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0435\u043c\u0443 \u0442\u0430\u043a\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">loggingOpts := []logging.Option{     logging.WithLogOnEvents(         logging.PayloadReceived, logging.PayloadSent,     ), }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u0441\u043e\u043e\u0431\u0449\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u0443, \u0447\u0442\u043e, \u043f\u043e\u043c\u0438\u043c\u043e \u043f\u0440\u043e\u0447\u0435\u0433\u043e, \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0445\u043e\u0442\u0438\u043c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043b\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u043e\u0442\u0432\u0435\u0442\u0430. \u0422\u0430\u043a\u0430\u044f \u043e\u043f\u0446\u0438\u044f \u0434\u0430\u043b\u0435\u043a\u043e \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0443\u043c\u0435\u0441\u0442\u043d\u0430, \u0442.\u043a. \u0432 \u0442\u0435\u043b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0438 \u043a\u0430\u043a \u043d\u0435\u043b\u044c\u0437\u044f. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u043f\u0430\u0440\u043e\u043b\u0438 \u0432 \u0447\u0438\u0441\u0442\u043e\u043c \u0432\u0438\u0434\u0435. <\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f, \u044f \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0432\u0430\u043c \u0434\u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043c\u0430\u0441\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0432 \u043b\u043e\u0433\u0430\u0445 \u2014 \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0432 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b\u0439 \u0432\u044b\u0448\u0435 <code>InterceptorLogger()<\/code>. \u041c\u0430\u0441\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438: \u0437\u0430\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0441\u0438\u043c\u0432\u043e\u043b\u0430\u043c\u0438 \u0437\u0432\u0435\u0437\u0434\u043e\u0447\u043a\u0438, \u0437\u0430\u043c\u0435\u043d\u044f\u0442\u044c \u0437\u0432\u0435\u0437\u0434\u043e\u0447\u043a\u0430\u043c\u0438 \u043b\u0438\u0448\u044c \u0447\u0430\u0441\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044f \u0438 \u0442.\u043f. <\/p>\n<p>  <\/p>\n<blockquote><p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u043c\u0430\u0441\u043a\u0438\u0440\u0443\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c, \u044d\u0442\u043e \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0434\u044b\u0440\u0430 \u0432 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438! \u0412 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043b\u0443\u0447\u0448\u0435 \u0432\u043e\u043e\u0431\u0449\u0435 \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043b\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430.<\/p><\/blockquote>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u0441\u0451 \u044d\u0442\u043e \u043a \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440, \u0438 \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/app\/grpc\/app.go  import (     \"log\/slog\"      authgrpc \"grpc-service-ref\/internal\/grpc\/auth\"      \"github.com\/grpc-ecosystem\/go-grpc-middleware\/v2\/interceptors\/logging\"     \"github.com\/grpc-ecosystem\/go-grpc-middleware\/v2\/interceptors\/recovery\"     \"google.golang.org\/grpc\"     \"google.golang.org\/grpc\/codes\"     \"google.golang.org\/grpc\/status\" )  \/\/ ...  \/\/ New creates new gRPC server app. func New(     log *slog.Logger,     authService authgrpc.Auth,     port int, ) *App {     loggingOpts := []logging.Option{         logging.WithLogOnEvents(             logging.PayloadReceived, logging.PayloadSent,         ),     }      recoveryOpts := []recovery.Option{         recovery.WithRecoveryHandler(func(p interface{}) (err error) {             log.Error(\"Recovered from panic\", slog.Any(\"panic\", p))              return status.Errorf(codes.Internal, \"internal error\")         }),     }      gRPCServer := grpc.NewServer(grpc.ChainUnaryInterceptor(         recovery.UnaryServerInterceptor(recoveryOpts...),         logging.UnaryServerInterceptor(InterceptorLogger(log), loggingOpts...),     ))      authgrpc.Register(gRPCServer, authService)      return &amp;App{         log:        log,         gRPCServer: gRPCServer,         port:       port,     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u044d\u0442\u0438\u0445 \u0434\u0432\u0443\u0445, \u0432\u0430\u043c \u043c\u043e\u0433\u0443\u0442 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u044b \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0446\u0435\u043b\u0435\u0439: \u0442\u0440\u0435\u0439\u0441\u0438\u043d\u0433, \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u0430\u043b\u0435\u0440\u0442\u044b, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u0434\u0440. \u041d\u043e \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0430\u043c \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0437\u0434\u0435\u0441\u044c \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>authgrpc.Register<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 (<code>authService<\/code>) \u043d\u0430 \u043d\u0430\u0448\u0435\u043c gRPC \u0441\u0435\u0440\u0432\u0435\u0440\u0435 (<code>gRPCServer<\/code>). \u0412 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 gRPC \u044d\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0431\u0443\u0434\u0435\u0442 \u0437\u043d\u0430\u0442\u044c, \u043a\u0430\u043a \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 RPC-\u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 \u044d\u0442\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 (\u043c\u0435\u0442\u043e\u0434\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u043d\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442) \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u0432\u044f\u0437\u0430\u043d\u044b \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c gRPC.<\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043b\u0438\u0448\u044c \u043d\u0430\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/app\/grpc\/app.go  \/\/ MustRun runs gRPC server and panics if any error occurs. func (a *App) MustRun() {     if err := a.Run(); err != nil {         panic(err)     } }  \/\/ Run runs gRPC server. func (a *App) Run() error {     const op = \"grpcapp.Run\"      \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c listener, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0443\u0448\u0438\u0442\u044c TCP-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0430\u0434\u0440\u0435\u0441\u043e\u0432\u0430\u043d\u043d\u044b\u0435     \/\/ \u041d\u0430\u0448\u0435\u043c\u0443 gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u0443     l, err := net.Listen(\"tcp\", fmt.Sprintf(\":%d\", a.port))     if err != nil {         return fmt.Errorf(\"%s: %w\", op, err)     }      a.log.Info(\"grpc server started\", slog.String(\"addr\", l.Addr().String()))      \/\/ \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a gRPC-\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439     if err := a.gRPCServer.Serve(l); err != nil {         return fmt.Errorf(\"%s: %w\", op, err)     }      return nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c gRPC-\u0441\u0435\u0440\u0432\u0438\u0441, \u0442\u043e \u043d\u0435\u0442 \u0441\u043c\u044b\u0441\u043b\u0430 \u0438\u0434\u0442\u0438 \u043f\u043e \u043a\u043e\u0434\u0443 \u0434\u0430\u043b\u044c\u0448\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u0436\u0435\u043c \u0441\u043c\u0435\u043b\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>MustRun()<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u043c \u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u043f\u0430\u043d\u0438\u043a\u043e\u0439, \u0435\u0441\u043b\u0438 \u0441\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u043a\u0430\u043a\u0430\u044f-\u0442\u043e \u043e\u0448\u0438\u0431\u043a\u0430. <\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c <code>grpcApp<\/code> \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/app\/app.go  package app  import (     \"log\/slog\"     \"time\"      grpcapp \"grpc-service-ref\/internal\/app\/grpc\"     \"grpc-service-ref\/internal\/services\/auth\"     \"grpc-service-ref\/internal\/storage\/sqlite\" )  type App struct {     GRPCServer *grpcapp.App }  func New(     log *slog.Logger,     grpcPort int,     storagePath string,     tokenTTL time.Duration, ) *App {     storage, err := sqlite.New(storagePath)     if err != nil {         panic(err)     }      authService := auth.New(log, storage, storage, storage, tokenTTL)      grpcApp := grpcapp.New(log, authService, grpcPort)      return &amp;App{         GRPCServer: grpcApp,     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u043d\u0438\u043c\u0430\u044e, \u0447\u0442\u043e \u043c\u043d\u043e\u0433\u0438\u0445 \u043c\u043e\u0436\u0435\u0442 \u0441\u043c\u0443\u0449\u0430\u0442\u044c \u044d\u0442\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">authService := auth.New(log, storage, storage, storage, tokenTTL)<\/code><\/pre>\n<p>  <\/p>\n<p>\u0410 \u0438\u043c\u0435\u043d\u043d\u043e, \u0442\u0440\u0438\u0436\u0434\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0439 <code>storage<\/code>. \u0423\u0432\u044b, \u0442\u0430\u043a\u043e\u0432\u044b \u0438\u0437\u0434\u0435\u0440\u0436\u043a\u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432. \u041d\u043e \u043f\u043e\u0434\u0443\u043c\u0430\u0439\u0442\u0435 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043d\u0435 \u0432\u043e \u0432\u0441\u0435\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u043c\u0438 \u044d\u0442\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c <code>storage<\/code>, \u044d\u0442\u043e \u0434\u0430\u0451\u0442 \u043d\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u0438. \u0412 \u043b\u044e\u0431\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u044d\u0442\u0430 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f \u0432\u0430\u043c \u043d\u0435 \u043f\u043e \u0434\u0443\u0448\u0435, \u0432\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043e\u043b\u044c\u043d\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e \u0441\u0432\u043e\u0435\u043c\u0443.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 <code>cmd\/sso<\/code>. \u0412 \u0438\u0442\u043e\u0433\u0435, \u043d\u0430\u0448 <code>main.go<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ cmd\/sso\/main.go  package main  import (     \"log\/slog\"     \"os\"      \"grpc-service-ref\/internal\/app\"     \"grpc-service-ref\/internal\/config\" )  const (     envLocal = \"local\"     envDev   = \"dev\"     envProd  = \"prod\" )  func main() {     cfg := config.MustLoad()      log := setupLogger(cfg.Env)      application := app.New(log, cfg.GRPC.Port, cfg.StoragePath, cfg.TokenTTL)      application.GRPCServer.MustRun() }  func setupLogger(env string) *slog.Logger {     \/\/ ... }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043c\u0435\u0441\u0442\u043e \u0441\u0442\u0440\u043e\u0447\u043a\u0438 <code>application.GRPCServer.MustRun()<\/code> \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0443\u0447\u0438\u0442\u044c \u0441\u0432\u043e\u0451 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0432\u0441\u0435 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435, \u0430 \u043d\u0435 \u0434\u0451\u0440\u0433\u0430\u0442\u044c \u0437\u0430\u043f\u0443\u0441\u043a \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0432 <code>main()<\/code>. \u0422\u043e \u0435\u0441\u0442\u044c, \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">application.MustRun()<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u043e \u043c\u043d\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435. <\/p>\n<p>  <\/p>\n<h3 id=\"graceful-shutdown---pravilnaya-ostanovka-prilozheniya\">Graceful shutdown \u2014 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h3>\n<p>  <\/p>\n<p>\u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0432\u0438\u0434\u0435 \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u0435\u043a\u0440\u0430\u0441\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0443\u0447\u0438\u043c \u0435\u0433\u043e \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0435\u043a\u0440\u0430\u0441\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438. \u0420\u0435\u0447\u044c, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u043f\u0440\u043e <em>graceful shutdown<\/em>.<\/p>\n<p>  <\/p>\n<blockquote><p><strong>Graceful shutdown<\/strong> \u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0430\u0435\u0442 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043f\u0435\u0440\u0435\u0434 \u043e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c<\/p><\/blockquote>\n<p>\u041f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438, \u0435\u0441\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u0442, \u0447\u0442\u043e \u0435\u0433\u043e \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u043b\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443, \u043e\u043d\u043e \u043f\u0435\u0440\u0435\u0434 \u044d\u0442\u0438\u043c \u0434\u043e\u043b\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0430\u0436\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f: \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0435 \u043e\u0431\u0440\u044b\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0431\u043e\u0442\u044b.<\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0443\u0447\u0438\u0442\u044c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 <code>grpcApp<\/code> \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u043c\u0443 \u043c\u0435\u0442\u043e\u0434 <code>Stop()<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/app\/grpc\/app.go  \/\/ Stop stops gRPC server. func (a *App) Stop() {     const op = \"grpcapp.Stop\"      a.log.With(slog.String(\"op\", op)).         Info(\"stopping gRPC server\", slog.Int(\"port\", a.port))      \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0432 gRPCServer \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c graceful shutdown     a.gRPCServer.GracefulStop() }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u043c, \u043d\u0430\u043c \u043f\u043e\u0447\u0442\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u2014 \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u043c \u043d\u0430\u043c\u0438 <code>gRPCServer *grpc.Server<\/code> \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u043c\u0435\u0442\u043e\u0434. \u0427\u0442\u043e \u043e\u043d \u0434\u0435\u043b\u0430\u0435\u0442:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u041f\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b<\/li>\n<li>\u0416\u0434\u0451\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432<\/li>\n<li>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432 <code>main()<\/code> \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0443\u0447\u0438\u0442\u044c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0441\u0438\u0433\u043d\u0430\u043b\u044b <code>SIGINT<\/code> \u0438 <code>SIGTERM<\/code> \u043e\u0442 \u041e\u0421 (\u0442.\u0435. \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u043a\u043e\u0433\u0434\u0430 \u043e\u0442 \u041e\u0421 \u043f\u0440\u0438\u0448\u043b\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0440\u0430\u0431\u043e\u0442\u044b). \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043a\u0430\u043d\u0430\u043b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u0441\u0438\u0433\u043d\u0430\u043b\u0430\u0445 stop := make(chan os.Signal, 1) \/\/ \"\u0421\u043b\u0443\u0448\u0430\u0435\u043c\" \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u0441\u0438\u0433\u043d\u0430\u043b\u044b signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)  \/\/ \u0416\u0434\u0451\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u043a\u0430\u043d\u0430\u043b\u0430 &lt;-stop  \/\/ TODO: \u0437\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u0442 \u043a\u043e\u0434 graceful shutdown<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u043a\u0430 \u0441\u0438\u0433\u043d\u0430\u043b\u043e\u0432 \u043d\u0435\u0442, \u043a\u043e\u043c\u0430\u043d\u0434\u0430 <code>&lt;-stop<\/code> \u0431\u0443\u0434\u0435\u0442 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430, \u0442.\u0435. \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c, \u0438 \u043c\u044b \u043d\u0435 \u0432\u044b\u0439\u0434\u0435\u043c \u0438\u0437 <code>main()<\/code> \u0440\u0430\u043d\u044c\u0448\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438\u0434\u0435\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0441\u0438\u0433\u043d\u0430\u043b, \u0432 \u043a\u0430\u043d\u0430\u043b <code>stop<\/code> \u043f\u0440\u0438\u0434\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u0438 \u043c\u044b \u0434\u0432\u0438\u043d\u0435\u043c\u0441\u044f \u0434\u0430\u043b\u044c\u0448\u0435 \u2014 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c \u043a\u043e\u0434 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0438 \u0432\u044b\u0439\u0434\u0435\u043c \u0438\u0437 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b.<\/p>\n<p>  <\/p>\n<p>\u041d\u043e \u043f\u0435\u0440\u0435\u0434 \u044d\u0442\u0438\u043c \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043c, \u0447\u0442\u043e \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0442\u043e\u0436\u0435 \u0431\u044b\u043b\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0430\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0451 \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e, \u0442.\u0435.: <code>go application.GRPCServer.MustRun()<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0441\u0451 \u044d\u0442\u043e \u0432\u043c\u0435\u0441\u0442\u0435 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ cmd\/sso\/main.go  import (     \"os\"     \"os\/signal\"     \"syscall\" )  func main() {     \/\/ ...      go func() {         application.GRPCServer.MustRun()     }()      \/\/ Graceful shutdown      stop := make(chan os.Signal, 1)     signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)      \/\/ Waiting for SIGINT (pkill -2) or SIGTERM     &lt;-stop      \/\/ initiate graceful shutdown     application.GRPCServer.Stop() \/\/ Assuming GRPCServer has Stop() method for graceful shutdown     log.Info(\"Gracefully stopped\") }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0432\u0430\u043c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 <code>Stop()<\/code> \u0434\u043b\u044f sqlite-\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 <code>Storage<\/code>. \u0422\u0430\u043c \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u0436\u0435 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u043e\u0439: <code>s.db.Close()<\/code>. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c <code>Storage<\/code> \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 <code>App<\/code> \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (<code>internal\/app\/app.go<\/code>). \u041f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438, \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 <code>StorageApp<\/code> \u2014 \u044d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u043f\u043e\u0434\u0445\u043e\u0434.<\/p>\n<p>  <a name=\"8\"><\/a>  <\/p>\n<h2 id=\"funkcionalnye-testy\">\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/h2>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0438 \u0432 <a href=\"https:\/\/habr.com\/ru\/companies\/selectel\/articles\/747738\/\">\u0441\u0442\u0430\u0442\u044c\u0435<\/a> \/ <a href=\"https:\/\/youtu.be\/rCJvW2xgnk0\" rel=\"nofollow noopener noreferrer\">\u0440\u043e\u043b\u0438\u043a\u0435<\/a> \u043f\u0440\u043e REST API, \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043a \u0447\u0451\u0440\u043d\u0443\u044e \u043a\u043e\u0440\u043e\u0431\u043a\u0443. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0447\u0435\u0441\u0442\u043d\u043e \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u043e\u0437\u0440\u0435\u0432\u0430\u0442\u044c \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u044e\u0442, \u0438 \u0431\u0443\u0434\u0435\u043c \u0447\u0435\u0441\u0442\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0432 \u043d\u0435\u0433\u043e \u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b.<\/p>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0438\u0435 \u0442\u0435\u0441\u0442\u044b \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u0439 \u0432\u0435\u0441\u044c \u0444\u043b\u043e\u0443 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0438 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0433\u0438\u0431\u043a\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043b\u044e\u0431\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043a\u0435\u0439\u0441\u044b. \u0422\u0430\u043a\u0436\u0435 \u043e\u043d\u0438 \u0443\u0434\u043e\u0431\u043d\u043e \u0442\u0435\u043c, \u0447\u0442\u043e \u0438\u0445 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0442\u0435\u0445 \u0436\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u0447\u0435\u0440\u043a\u043d\u0443, \u0447\u0442\u043e \u0442\u0435\u0441\u0442\u044b \u043d\u0443\u0436\u043d\u044b \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0431\u043e\u0442\u044b, \u043e\u043d\u0438 \u0442\u0430\u043a\u0436\u0435 \u0441\u043b\u0443\u0436\u0430\u0442 \u0443\u0434\u043e\u0431\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0447\u0442\u043e-\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c. \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e, \u043a\u043e\u0433\u0434\u0430 \u044d\u0442\u043e \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u0440\u0435\u0434\u043a\u0438\u0439 \u043a\u0435\u0439\u0441. \u041d\u0430\u043c \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u0434\u0435\u0431\u0430\u0433\u0430 \u0438 \u0442.\u043f.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u043e\u0431\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0440\u043e\u0439 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u044b\u043c\u0438, \u0442.\u043a. \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0430\u043b\u0438\u0447\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 (\u0442.\u0435. \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043d\u0443\u0436\u043d\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c), \u043e\u043d\u0438 \u0437\u0430\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438 \u043c\u043e\u043a\u0430\u043c\u0438. \u0418\u043c\u0435\u043d\u043d\u043e \u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u0441\u0451 \u0435\u0449\u0451 \u0434\u0443\u043c\u0430\u0435\u0442, \u0447\u0442\u043e \u043e\u043d\u043e \u0447\u0435\u0441\u0442\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438 \u0447\u0435\u0441\u0442\u043d\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 \u0441 \u0441\u043e\u0441\u0435\u0434\u044f\u043c\u0438.<\/p>\n<p>  <\/p>\n<p>\u0415\u0449\u0451 \u043e\u0434\u043d\u043e \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0438\u0445 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043c\u044b \u043d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u043f\u0438\u0441\u0430\u0442\u044c gRPC-\u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u0410 \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u043a\u0430\u043a \u043f\u043e\u043b\u0438\u0433\u043e\u043d \u0434\u043b\u044f \u0441\u0432\u043e\u0438\u0445 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u043e\u0432, \u0438\u0433\u0440\u0430\u044f\u0441\u044c \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u043f\u044b\u0442\u0430\u044f\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0445\u0438\u0442\u0440\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u043a\u0435\u0439\u0441\u044b \u0438 \u0442.\u043f.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c. \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0438\u043c \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u2014 <code>tests\/<\/code>. \u0417\u0430\u0431\u0435\u0433\u0430\u044f \u0432\u043f\u0435\u0440\u0435\u0434, \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">.\/tests \u251c\u2500\u2500 auth_register_login_test.go ... \u0422\u0435\u0441\u0442-\u043a\u0435\u0439\u0441\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 Login  \u251c\u2500\u2500 some_other_case_test.go ....... \u041a\u0430\u043a\u0438\u0435-\u0442\u043e \u0434\u0440\u0443\u0433\u0438\u0435 \u043a\u0435\u0439\u0441\u044b \u251c\u2500\u2500 migrations .................... \u041c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u2502\u00a0\u00a0 \u2514\u2500\u2500 1_init_apps.up.sql \u2514\u2500\u2500 suite     \u2514\u2500\u2500 suite.go .................. \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0432\u0441\u0435\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0433\u043e \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0412 \u043a\u043e\u0440\u043d\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>tests<\/code> \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c \u0441\u0430\u043c\u0438 \u0444\u0430\u0439\u043b\u044b \u0442\u0435\u0441\u0442\u043e\u0432 \u0441 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c\u0438 \u043a\u0435\u0439\u0441\u0430\u043c\u0438 (\u0443 \u043d\u0438\u0445 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0441\u0443\u0444\u0444\u0438\u043a\u0441 <code>_test.go<\/code>)<\/li>\n<li><code>tests\/migrations<\/code> \u2014 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432. \u041e\u0431\u044b\u0447\u043d\u043e \u044f \u0438\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0411\u0414 \u0441\u0430\u043c\u044b\u043c\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 <code>apps<\/code>.<\/li>\n<li><code>tests\/suite<\/code> \u2014 \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0432\u0441\u0451, \u0447\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0442\u0435\u0441\u0442\u0443. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0411\u0414, \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 gRPC-\u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u0434\u0440.<\/li>\n<\/ul>\n<p>  <\/p>\n<h3 id=\"migracii-dlya-testov\">\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432<\/h3>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u2014 \u0441 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u043d\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"sql\">-- tests\/migrations\/1_init_apps.up.sql  INSERT INTO apps (id, name, secret)   VALUES (1, 'test', 'test-secret')  ON CONFLICT DO NOTHING;<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u043d\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0443 <code>ON CONFLICT DO NOTHING<\/code> \u2014 \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0435\u0441\u0442\u044c, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u0442. \u0422\u0430\u043a\u0436\u0435 \u0434\u0435\u0440\u0436\u0438\u043c \u0432 \u0433\u043e\u043b\u043e\u0432\u0435, \u0447\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 <code>id<\/code> \u0438 <code>secret<\/code> \u043d\u0430\u043c \u0441\u043a\u043e\u0440\u043e \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u0432 \u0442\u0435\u0441\u0442\u0430\u0445.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u043d\u0443\u044e \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e \u044f \u0437\u0434\u0435\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u044e, \u0442.\u043a. \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u0431\u0430\u0437\u0435 \u043c\u043d\u0435 \u044d\u0442\u043e \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0438\u0437 \u044d\u0442\u043e\u0439 \u043f\u0430\u043f\u043a\u0438 \u0431\u0443\u0434\u0435\u043c \u0442\u043e\u0439 \u0436\u0435 \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0439, \u043d\u043e \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go run .\/cmd\/migrator --storage-path=.\/storage\/sso.db --migrations-path=.\/tests\/migrations --migrations-table=migrations_test<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c, \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f \u043d\u0438\u0445 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443 <code>migrations_test<\/code>, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0431\u044b\u043b\u0438 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b \u043e\u0442 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445. \u0422\u0430\u043a\u0436\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0443\u0442\u044c \u0434\u043e \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439.<\/p>\n<p>  <\/p>\n<h3 id=\"suite---podgotovka-k-testu\">Suite \u2014 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043a \u0442\u0435\u0441\u0442\u0443<\/h3>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c <code>Suite<\/code>. \u041e\u043d \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0437 \u0441\u0435\u0431\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0432\u0441\u0451 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ tests\/suite\/suite.go  import (     \"testing\"     \"grpc-service-ref\/internal\/config\"     ssov1 \"github.com\/GolangLessons\/protos\/gen\/go\/sso\" )  type Suite struct {     *testing.T                  \/\/ \u041f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 *testing.T     Cfg        *config.Config   \/\/ \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f     AuthClient ssov1.AuthClient \/\/ \u041a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c Auth }<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><code>*testing.T<\/code> \u2014 \u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0442\u0435\u0441\u0442\u043e\u043c, \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/golang.org\/pkg\/testing\/#T\" rel=\"nofollow noopener noreferrer\">\u0442\u0443\u0442<\/a><\/li>\n<li><code>Cfg<\/code> \u2014 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0430, \u0442\u043e\u0442 \u0436\u0435 \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0437 <code>cmd<\/code><\/li>\n<li><code>AuthClient<\/code> \u2014 gRPC-\u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0430\u0448\u0435\u0433\u043e Auth-\u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Suit&#8217;\u0430, \u0441 \u0435\u0433\u043e \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0431\u0443\u0434\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0432 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ tests\/suite\/suite.go  import (     \"testing\"     \"context\" )  const configPath  \/\/ New creates new test suite. func New(t *testing.T) (context.Context, *Suite) {     t.Helper()   \/\/ \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432     t.Parallel() \/\/ \u0420\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432      \/\/ \u0427\u0438\u0442\u0430\u0435\u043c \u043a\u043e\u043d\u0444\u0438\u0433 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430     cfg := config.MustLoadPath(configPath)      \/\/ \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442        ctx, cancelCtx := context.WithTimeout(context.Background(), cfg.GRPC.Timeout)      \/\/ \u041a\u043e\u0433\u0434\u0430 \u0442\u0435\u0441\u0442\u044b \u043f\u0440\u043e\u0439\u0434\u0443\u0442, \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442     t.Cleanup(func() {         t.Helper()         cancelCtx()     })      \/\/ \u0410\u0434\u0440\u0435\u0441 \u043d\u0430\u0448\u0435\u0433\u043e gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u0430     grpcAddress := net.JoinHostPort(grpcHost, strconv.Itoa(cfg.GRPC.Port))      \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442     cc, err := grpc.DialContext(context.Background(),         grpcAddress,         \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c insecure-\u043a\u043e\u043d\u043d\u0435\u043a\u0442 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432         grpc.WithTransportCredentials(insecure.NewCredentials()))      if err != nil {         t.Fatalf(\"grpc server connection failed: %v\", err)     }      \/\/ gRPC-\u043a\u043b\u0438\u0435\u043d\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Auth     authClient := ssov1.NewAuthClient(cc)      return ctx, &amp;Suite{         T:          t,         Cfg:        cfg,         AuthClient: authClient,     } }<\/code><\/pre>\n<p>  <\/p>\n<p><code>t.Helper()<\/code> \u2014 \u043f\u043e\u043c\u0435\u0447\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043a\u0430\u043a \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e, \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u044b\u0432\u043e\u0434\u0430 \u0442\u0435\u0441\u0442\u043e\u0432, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u043f\u0440\u0438 \u0438\u0445 \u043f\u0430\u0434\u0435\u043d\u0438\u0438. \u0410 \u0438\u043c\u0435\u043d\u043d\u043e, \u0447\u0442\u043e-\u0442\u043e \u0441\u0444\u044d\u0439\u043b\u0438\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u0445\u0435\u043b\u043f\u0435\u0440\u0430, \u0432 \u0432\u044b\u0432\u043e\u0434\u0435 \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u0430 \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f (\u043d\u043e \u0432 \u0442\u0440\u0435\u0439\u0441\u0435 \u0442\u0435\u043a\u0443\u0449\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442, \u043a\u043e\u043d\u0435\u0447\u043d\u043e), \u0447\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e \u043f\u0440\u0438 \u043e\u0442\u043b\u0430\u0434\u043a\u0435.<\/p>\n<p>  <\/p>\n<p>\u0418 \u0432\u043e\u0442 \u0442\u0443\u0442 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043d\u043e\u0432\u0430 \u043f\u0440\u043e\u0447\u0443\u0432\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043a\u0440\u0443\u0442\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0437 Protobuf: \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u043e\u0439 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u0430: <code>authClient := ssov1.NewAuthClient(cc)<\/code>. \u0415\u0441\u043b\u0438 \u0440\u0430\u043d\u044c\u0448\u0435 \u0432\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0441 \u0432\u0435\u0431-\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438 \u0431\u0435\u0437 \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438, \u0432\u0430\u043c \u0442\u043e\u0447\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u044d\u0442\u043e \u043f\u043e\u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f ?<\/p>\n<p>  <\/p>\n<p>\u041c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u0431\u0443\u0434\u0443\u0442 \u043c\u0435\u0442\u043e\u0434\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0432 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0435, \u0442.\u0435.: <code>authClient.Login()<\/code> \u0438 <code>authClient.Register()<\/code><\/p>\n<p>  <\/p>\n<h3 id=\"test-keys-happypath-dlya-metoda-login\">\u0422\u0435\u0441\u0442-\u043a\u0435\u0439\u0441 HappyPath \u0434\u043b\u044f \u043c\u0435\u0442\u043e\u0434\u0430 <code>Login()<\/code><\/h3>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u0436, \u0443 \u043d\u0430\u0441 \u0432\u0441\u0451 \u0433\u043e\u0442\u043e\u0432\u043e \u0434\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0442\u0435\u0441\u0442-\u043a\u0435\u0439\u0441\u0430. \u041d\u0430\u0447\u043d\u0451\u043c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u043e\u0433\u043e <a href=\"https:\/\/en.wikipedia.org\/wiki\/Happy_path\" rel=\"nofollow noopener noreferrer\">Happy Path<\/a> \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 (\u044d\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u0443 \u043d\u0430\u0441 \u0432\u0441\u0451 \u0438\u0434\u0435\u0442 \u0447\u0435\u0442\u043a\u043e \u043f\u043e \u043f\u043b\u0430\u043d\u0443). \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043f\u0440\u0438\u0441\u0435\u0434\u0430\u043d\u0438\u044f \u0438 \u043d\u0430\u043c\u0435\u0442\u0438\u043c \u043f\u043b\u0430\u043d \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ tests\/auth_register_login_test.go package tests  import (     \"testing\"     \"grpc-service-ref\/tests\/suite\" )  const (     appID = 1                 \/\/ ID \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0435\u0439     appSecret = \"test-secret\" \/\/ \u0421\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f )  func TestRegisterLogin_Login_HappyPath(t *testing.T) {     ctx, st := suite.New(t) \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c Suite      \/\/ TODO: \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 (\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0435)      \/\/ TODO: \u0421\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b      \/\/ TODO: \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c, \u0442.\u043a. \u043c\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u0435\u0442\u043e\u0434 RPC-\u043c\u0435\u0442\u043e\u0434 <code>Login()<\/code>. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u0430\u043c \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u0434\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c. \u041f\u043e\u0447\u0435\u043c\u0443:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u042f \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u043e\u0447\u0438\u0449\u0430\u044e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432, \u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0443 \u043d\u0430\u0441 \u0431\u044b\u0442\u044c \u043d\u0435 \u043c\u043e\u0436\u0435\u0442<\/li>\n<li>\u042d\u0442\u043e \u043f\u043e\u0432\u044b\u0448\u0430\u0435\u0442 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0435\u0441\u0442\u043e\u0432, \u0442.\u043a. \u043c\u044b \u043d\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0434\u0430\u043d\u043d\u044b\u043c, \u0431\u043e\u043b\u044c\u0448\u0435 \u0448\u0430\u043d\u0441\u043e\u0432 \u043f\u043e\u0439\u043c\u0430\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u0440\u0435\u0434\u043a\u0438\u0439 \u0431\u0430\u0433<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041f\u043e\u0447\u0435\u043c\u0443 \u044f \u043d\u0435 \u043e\u0447\u0438\u0449\u0430\u044e \u0411\u0414 \u043f\u043e\u0441\u043b\u0435 \u0442\u0435\u0441\u0442\u043e\u0432? \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c (\u043f\u0443\u0441\u0442\u044c \u0438 \u043d\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e), \u0447\u0442\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0441\u0435\u0431\u044f \u0432\u0435\u0434\u0451\u0442 \u043f\u0440\u0438 \u043b\u044e\u0431\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u0445 \u0411\u0414 \u2014 \u043a\u043e\u0433\u0434\u0430 \u0442\u0430\u043c \u043f\u0443\u0441\u0442\u043e, \u043a\u043e\u0433\u0434\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u043d\u043e\u0433\u043e, \u043a\u043e\u0433\u0434\u0430 \u0441\u0440\u0435\u0434\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0435\u0441\u0442\u044c \u0441\u043b\u043e\u043c\u0430\u043d\u043d\u044b\u0435 \u0438 \u0442.\u043f. \u041b\u0438\u0448\u043d\u0438\u043c \u044d\u0442\u043e \u0442\u043e\u0447\u043d\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u0442. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0432\u0441\u0435\u0433\u0434\u0430 \u0443\u0434\u043e\u0431\u043d\u043e \u0438\u043c\u0435\u0442\u044c \u043f\u043e\u0434 \u0440\u0443\u043a\u043e\u0439 \u043d\u0435 \u043f\u0443\u0441\u0442\u0443\u044e \u0411\u0414, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0436\u0438\u0432\u0435\u0442 \u0441\u0432\u043e\u0435\u0439 \u0436\u0438\u0437\u043d\u044c \u0438 \u043f\u043e\u0441\u0442\u0435\u043f\u0435\u043d\u043d\u043e \u043f\u043e\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u041d\u043e \u0432 GitHub Actions (\u0438\u043b\u0438, \u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0432 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0435 GitLab), \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0431\u0443\u0434\u0435\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f \u0441\u0432\u0435\u0436\u0430\u044f \u0411\u0414, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <code>brianvoe\/gofakeit<\/code> \u2014 \u043e\u0447\u0435\u043d\u044c \u043a\u0440\u0443\u0442\u0430\u044f \u0448\u0442\u0443\u043a\u0430, \u043c\u043e\u0436\u0435\u0442 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0432\u0438\u0434\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0435\u0451:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">go get github.com\/brianvoe\/gofakeit\/v6@v6.23.2<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0435 email \u0438 \u043f\u0430\u0440\u043e\u043b\u044c (\u043d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e email \u0441\u043b\u0443\u0436\u0438\u0442 \u0443 \u043d\u0430\u0441 \u043b\u043e\u0433\u0438\u043d\u043e\u043c):<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">const passDefaultLen = 10  email := gofakeit.Email() pass := gofakeit.Password(true, true, true, true, false, passLen)<\/code><\/pre>\n<p>  <\/p>\n<p>\u0425\u043e\u0442\u044c \u043c\u043d\u0435 \u0438 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u044d\u0442\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043d\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441 \u0442\u0430\u043a\u0438\u043c\u0438 \u0443\u0436\u0430\u0441\u043d\u044b\u043c\u0438 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0430\u043c\u0438 \u044f \u043d\u0435\u043d\u0430\u0432\u0438\u0436\u0443. \u0423\u0432\u044b, \u043b\u0443\u0447\u0448\u0435\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u044f \u043f\u043e\u043a\u0430 \u043d\u0435 \u043d\u0430\u0448\u0435\u043b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0445\u043e\u0442\u044f \u0431\u044b \u043f\u0440\u0438\u0447\u0435\u0448\u0435\u043c \u0447\u0442\u043e \u0438\u043c\u0435\u0435\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">pass := randomFakePassword) \/\/ ...  func randomFakePassword() string {     return gofakeit.Password(true, true, true, true, false, passDefaultLen) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u0441\u0451 \u044d\u0442\u043e \u0432 \u0442\u0435\u0441\u0442 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c RPC-\u0437\u0430\u043f\u0440\u043e\u0441\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ tests\/auth_register_login_test.go package tests  import (     \"testing\"      \"grpc-service-ref\/tests\/suite\"      ssov1 \"github.com\/GolangLessons\/protos\/gen\/go\/sso\"     \"github.com\/stretchr\/testify\/assert\"     \"github.com\/stretchr\/testify\/require\"      \"github.com\/brianvoe\/gofakeit\/v6\" )  const (     appID  = 1     appKey = \"test-secret\"      passDefaultLen = 10 )  func TestRegisterLogin_Login_HappyPath(t *testing.T) {     ctx, st := suite.New(t)      email := gofakeit.Email()     pass := randomFakePassword()      \/\/ \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u043c \u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c     respReg, err := st.AuthClient.Register(ctx, &amp;ssov1.RegisterRequest{         Email:    email,         Password: pass,     })     \/\/ \u042d\u0442\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0435\u043b\u0430\u0435\u043c \u043b\u0438\u0448\u044c \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438     require.NoError(t, err)     assert.NotEmpty(t, respReg.GetUserId())      \/\/ \u0410 \u044d\u0442\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430     respLogin, err := st.AuthClient.Login(ctx, &amp;ssov1.LoginRequest{         Email:    email,         Password: pass,         AppId:    appID,     })     require.NoError(t, err)      \/\/ \u0422\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b:      \/\/ TODO: \u0414\u043e\u0441\u0442\u0430\u0432\u0451\u043c \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430 \u0442\u043e\u043a\u0435\u043d \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 (\u043a\u0440\u043e\u043c\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043d\u043e \u043c\u044b \u043f\u043e\u043a\u0430 \u0438 \u043d\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u043c \u0434\u0435\u043b\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043d\u0438\u043c\u0438), \u043c\u044b \u0447\u0435\u0441\u0442\u043d\u043e \u0438\u043c\u0438\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u2014 \u043e\u043d \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0441\u044f \u0438 \u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c\u0441\u044f. \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442, \u0447\u0442\u043e \u044d\u0442\u043e \u043b\u0438\u0448\u044c \u0442\u0435\u0441\u0442, \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u044d\u0442\u043e \u0432\u043f\u043e\u043b\u043d\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0432 \u044d\u0442\u043e\u043c \u0442\u0435\u0441\u0442\u0435 \u043c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0443 \u043c\u0435\u0442\u043e\u0434\u0430 <code>Login()<\/code>, \u0430 \u0441\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435 \u0432 \u043d\u0451\u043c \u2014 \u044d\u0442\u043e \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0441 \u043e\u0442\u0432\u0435\u0442\u043e\u043c \u043e\u0442 Auth-\u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0442\u0435 \u0436\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0432\u0435\u0440\u0448\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>jwt.Parse()<\/code> \u2014 \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/li>\n<li>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432\u0441\u0435\u0445 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u043f\u043e\u043b\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u0448\u043b\u0438 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0442\u043e\u043a\u0435\u043d\u043e\u043c: <code>uid<\/code>, <code>email<\/code>, <code>app_id<\/code><\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u044d\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432 \u043d\u0430\u0448 \u0442\u0435\u0441\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ tests\/auth_register_login_test.go package tests  import (     \"testing\"     \"time\"      \"grpc-service-ref\/tests\/suite\"      ssov1 \"github.com\/GolangLessons\/protos\/gen\/go\/sso\"     \"github.com\/golang-jwt\/jwt\/v5\"     \"github.com\/stretchr\/testify\/assert\"     \"github.com\/stretchr\/testify\/require\"      \"github.com\/brianvoe\/gofakeit\/v6\" )  func TestRegisterLogin_Login_HappyPath(t *testing.T) {     \/\/ .. \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b (\u0441\u043c. \u0432\u044b\u0448\u0435) ..     \/\/ ...      \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430     token := respLogin.GetToken()     require.NotEmpty(t, token) \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u043e\u043d \u043d\u0435 \u043f\u0443\u0441\u0442\u043e\u0439      \/\/ \u041e\u0442\u043c\u0435\u0447\u0430\u0435\u043c \u0432\u0440\u0435\u043c\u044f, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u043b\u043e\u0433\u0438\u043d.     \/\/ \u042d\u0442\u043e \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 TTL \u0442\u043e\u043a\u0435\u043d\u0430     loginTime := time.Now()      \/\/ \u041f\u0430\u0440\u0441\u0438\u043c \u0438 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0442\u043e\u043a\u0435\u043d     tokenParsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {         return []byte(appSecret), nil     })     \/\/ \u0415\u0441\u043b\u0438 \u043a\u043b\u044e\u0447 \u043e\u043a\u0430\u0436\u0435\u0442\u0441\u044f \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c, \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443     require.NoError(t, err)      \/\/ \u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c \u043a \u0442\u0438\u043f\u0443 jwt.MapClaims, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435     claims, ok := tokenParsed.Claims.(jwt.MapClaims)     require.True(t, ok)      \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0442\u043e\u043a\u0435\u043d\u0430     assert.Equal(t, respReg.GetUserId(), int64(claims[\"uid\"].(float64)))     assert.Equal(t, email, claims[\"email\"].(string))     assert.Equal(t, appID, int(claims[\"app_id\"].(float64)))      const deltaSeconds = 1      \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e TTL \u0442\u043e\u043a\u0435\u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043d\u0430\u0448\u0438\u043c \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f\u043c.     assert.InDelta(t, loginTime.Add(st.Cfg.TokenTTL).Unix(), claims[\"exp\"].(float64), deltaSeconds)<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u0440\u0435\u043c\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u044b \u043e\u0442\u043c\u0435\u0447\u0430\u0435\u043c \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <code>loginTime<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043d\u0435\u0442\u043e\u0447\u043d\u044b\u043c, \u0442.\u043a. \u0442\u043e\u0447\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440 Auth, \u0430 \u0434\u043b\u044f \u043d\u0430\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u043e\u043d, \u043a\u0430\u043a \u043c\u044b \u043f\u043e\u043c\u043d\u0438\u043c, \u0447\u0435\u0440\u043d\u0430\u044f \u043a\u043e\u0440\u043e\u0431\u043a\u0430. \u0422\u043e \u0435\u0441\u0442\u044c, \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043b\u0430\u0433 \u043c\u0435\u0436\u0434\u0443 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u043c \u043d\u0430\u043c\u0438 \u043e\u0442\u0432\u0435\u0442\u0430.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u043d\u0430\u0441 \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0438\u0442, \u043d\u043e \u043d\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>assert.InDelta<\/code> \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f <code>exp<\/code> (expiration time). \u0410 \u0438\u043c\u0435\u043d\u043d\u043e, \u043c\u044b \u0431\u0435\u0440\u0451\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u0432\u044b\u0448\u0435 <code>loginTime<\/code>, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a \u043d\u0435\u043c\u0443 <code>st.Cfg.TokenTTL<\/code>, \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c \u044d\u0442\u043e \u0432 UnixTimestamp (\u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u043b\u0438), \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u043f\u043e\u043b\u0435 <code>exp<\/code> \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u044d\u0442\u043e\u043c\u0443 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e \u0441 \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u044c\u044e \u0434\u043e \u043e\u0434\u043d\u043e\u0439 \u0441\u0435\u043a\u0443\u043d\u0434\u044b.<\/p>\n<p>  <\/p>\n<h3 id=\"zapusk-testov\">\u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432<\/h3>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0441\u0432\u0435\u0436\u0430\u044f \u0447\u0438\u0441\u0442\u0430\u044f \u0411\u0414, \u0442\u043e \u043f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go run .\/cmd\/migrator --storage-path=.\/storage\/sso.db --migrations-path=.\/migrations<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0430\u0442\u0435\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go run .\/cmd\/migrator --storage-path=.\/storage\/sso.db --migrations-path=.\/tests\/migrations --migrations-table=migrations_test<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go run .\/cmd\/sso --config=.\/config\/local_tests_config.yaml<\/code><\/pre>\n<p>  <\/p>\n<p>\u042f \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433-\u0444\u0430\u0439\u043b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0437\u0434\u0435\u0441\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u0438\u043c\u044f: <code>local_tests_config.yaml<\/code>. \u041d\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u0418 \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u0430\u043c\u0438 \u0442\u0435\u0441\u0442\u044b, \u0443\u043a\u0430\u0437\u0430\u0432 \u0433\u0434\u0435 \u043e\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go test .\/tests -count=1 -v<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>-count=1<\/code> \u2014 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0441 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043a\u044d\u0448\u0430, \u0430 <code>-v<\/code> \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0435\u0439 \u0432 \u0432\u044b\u0432\u043e\u0434 \u0442\u0435\u0441\u0442\u0430.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0432\u0441\u0451 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0432\u043e\u0442 \u0442\u0430\u043a\u043e\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">=== RUN   TestRegisterLogin_Login_HappyPath === PAUSE TestRegisterLogin_Login_HappyPath === CONT  TestRegisterLogin_Login_HappyPath --- PASS: TestRegisterLogin_Login_HappyPath (0.15s) PASS ok      grpc-service-ref\/tests  0.663s<\/code><\/pre>\n<p>  <\/p>\n<h3 id=\"negativnye-keysy\">\u041d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043a\u0435\u0439\u0441\u044b<\/h3>\n<p>  <\/p>\n<p>\u041e\u0431\u044b\u0447\u043d\u043e \u0441 \u0442\u0435\u0441\u0442\u0430\u0445 \u0441\u0430\u043c\u044b\u043c\u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c\u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043a\u0435\u0439\u0441\u044b. \u0422\u043e \u0435\u0441\u0442\u044c, \u043c\u044b \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u0441\u043b\u043e\u043c\u0430\u0442\u044c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u043e\u043d\u043e \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f \u0442\u0430\u043a, \u043a\u0430\u043a \u043c\u044b \u043e\u0436\u0438\u0434\u0430\u0435\u043c. \u042f \u043f\u043e\u043a\u0430\u0436\u0443 \u043b\u0438\u0448\u044c \u043f\u0430\u0440\u0443 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432, \u0430 \u0434\u0430\u043b\u044c\u0448\u0435 \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0432\u0430\u043c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u0442\u044c \u0441\u0430\u043c\u044b\u0435 \u0438\u0437\u043e\u0449\u0440\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0441\u043b\u043e\u043c\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043d\u0438\u0445 \u0442\u0435\u0441\u0442\u044b.<\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u0442\u0430\u043a? \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043f\u044b\u0442\u0430\u0442\u044c\u0441\u044f \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u0441 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u043c \u043b\u043e\u0433\u0438\u043d\u043e\u043c. \u041d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0442\u0430\u043a\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0442\u044c, \u0438 \u0434\u043e\u043b\u0436\u043d\u043e \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u043e\u0439. \u0418 \u0443\u0436 \u0442\u0435\u043c \u0431\u043e\u043b\u0435\u0435, \u043e\u043d\u043e \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u043f\u0430\u043d\u0438\u043a\u043e\u0439, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440. \u0422\u0435\u0441 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">func TestRegisterLogin_DuplicatedRegistration(t *testing.T) {     ctx, st := suite.New(t)      email := gofakeit.Email()     pass := randomFakePassword()      \/\/ \u041f\u0435\u0440\u0432\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439     respReg, err := st.AuthClient.Register(ctx, &amp;ssov1.RegisterRequest{         Email:    email,         Password: pass,     })     require.NoError(t, err)     require.NotEmpty(t, respReg.GetUserId())      \/\/ \u0412\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 - \u0444\u044d\u0438\u043b     respReg, err = st.AuthClient.Register(ctx, &amp;ssov1.RegisterRequest{         Email:    email,         Password: pass,     })     require.Error(t, err)     assert.Empty(t, respReg.GetUserId())     assert.ErrorContains(t, err, \"user already exists\") }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0442\u044c \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u0432\u0445\u043e\u0434. \u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u043a\u0435\u0439\u0441\u043e\u0432 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e, \u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0442\u0430\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b (\u0435\u0441\u043b\u0438 \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b \u0441 \u043d\u0438\u043c\u0438, \u043e\u0447\u0435\u043d\u044c \u0441\u043e\u0432\u0435\u0442\u0443\u044e <a href=\"https:\/\/dave.cheney.net\/2019\/05\/07\/prefer-table-driven-tests\" rel=\"nofollow noopener noreferrer\">\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f<\/a>). \u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">func TestRegister_FailCases(t *testing.T) {     ctx, st := suite.New(t)      tests := []struct {         name        string         email       string         password    string         expectedErr string     }{         {             name:        \"Register with Empty Password\",             email:       gofakeit.Email(),             password:    \"\",             expectedErr: \"password is required\",         },         {             name:        \"Register with Empty Email\",             email:       \"\",             password:    randomFakePassword(),             expectedErr: \"email is required\",         },         {             name:        \"Register with Both Empty\",             email:       \"\",             password:    \"\",             expectedErr: \"email is required\",         },     }      for _, tt := range tests {         t.Run(tt.name, func(t *testing.T) {             _, err := st.AuthClient.Register(ctx, &amp;ssov1.RegisterRequest{                 Email:    tt.email,                 Password: tt.password,             })             require.Error(t, err)             require.Contains(t, err.Error(), tt.expectedErr)          })     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u043d\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">func TestLogin_FailCases(t *testing.T) {     ctx, st := suite.New(t)      tests := []struct {         name        string         email       string         password    string         appID       int32         expectedErr string     }{         {             name:        \"Login with Empty Password\",             email:       gofakeit.Email(),             password:    \"\",             appID:       appID,             expectedErr: \"password is required\",         },         {             name:        \"Login with Empty Email\",             email:       \"\",             password:    randomFakePassword(),             appID:       appID,             expectedErr: \"email is required\",         },         {             name:        \"Login with Both Empty Email and Password\",             email:       \"\",             password:    \"\",             appID:       appID,             expectedErr: \"email is required\",         },         {             name:        \"Login with Non-Matching Password\",             email:       gofakeit.Email(),             password:    randomFakePassword(),             appID:       appID,             expectedErr: \"invalid email or password\",         },         {             name:        \"Login without AppID\",             email:       gofakeit.Email(),             password:    randomFakePassword(),             appID:       emptyAppID,             expectedErr: \"app_id is required\",         },     }      for _, tt := range tests {         t.Run(tt.name, func(t *testing.T) {             _, err := st.AuthClient.Register(ctx, &amp;ssov1.RegisterRequest{                 Email:    gofakeit.Email(),                 Password: randomFakePassword(),             })             require.NoError(t, err)              _, err = st.AuthClient.Login(ctx, &amp;ssov1.LoginRequest{                 Email:    tt.email,                 Password: tt.password,                 AppId:    tt.appID,             })             require.Error(t, err)             require.Contains(t, err.Error(), tt.expectedErr)         })     } }<\/code><\/pre>\n<p>  <a name=\"9\"><\/a>  <\/p>\n<h2 id=\"integraciya-s-vneshnim-servisom---url-shortener\">\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c \u2014 URL Shortener<\/h2>\n<p>  <\/p>\n<p>\u0427\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e gRPC \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u043d\u0435 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043d\u0430\u0448\u0435\u0433\u043e SSO \u043a \u043a\u043e\u043c\u0443-\u043d\u0438\u0431\u0443\u0434\u044c \u0432\u043d\u0435\u0448\u043d\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443. \u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0442\u0430\u043a\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0437\u044f\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442 \u0438\u043b\u0438 \u043d\u0430\u0431\u0440\u043e\u0441\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043d\u0430 \u043a\u043e\u043b\u0435\u043d\u043a\u0435.<\/p>\n<p>  <\/p>\n<p>\u042f \u0436\u0435 \u0431\u0443\u0434\u0443 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0432 \u0441\u0432\u043e\u0439 <a href=\"https:\/\/github.com\/GolangLessons\/url-shortener\" rel=\"nofollow noopener noreferrer\">URL Shortener<\/a>, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0443 \u043c\u0435\u043d\u044f \u0443\u0436\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/habr.com\/ru\/companies\/selectel\/articles\/747738\/\">\u0441\u0442\u0430\u0442\u044c\u044f<\/a> \u0438 <a href=\"https:\/\/youtu.be\/rCJvW2xgnk0?si=_JddTOCm1Y-xCgBx\" rel=\"nofollow noopener noreferrer\">\u0440\u043e\u043b\u0438\u043a<\/a>. \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u044f \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u044e \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0432 \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u0433\u0430\u0439\u0434\u0430\u0445 \u2014 \u0431\u0443\u0434\u0435\u0442 \u0438 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 URL Shortener&#8217;\u043e\u043c, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 Gateway, \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0438, \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u0432\u0441\u0451 \u044d\u0442\u043e \u0434\u0435\u043b\u043e \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u0442\u044c (\u0442\u0440\u0435\u0439\u0441\u044b, \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u0430\u043b\u0435\u0440\u0442\u044b, \u0441\u0431\u043e\u0440 \u043b\u043e\u0433\u043e\u0432 \u0438 \u0434\u0440). \u041d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u044e, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435, \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u043d\u0430 <a href=\"https:\/\/t.me\/ntuzov\" rel=\"nofollow noopener noreferrer\">\u043c\u043e\u0439 Telegram-\u043a\u0430\u043d\u0430\u043b<\/a> \u2014 \u0442\u0430\u043c \u044f \u0431\u0443\u0434\u0443 \u0441\u043e\u0432\u0435\u0442\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u043f\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0443 \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c\u0443 \u043d\u043e\u0432\u044b\u0445 \u0433\u0430\u0439\u0434\u043e\u0432, \u0430\u043d\u043e\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0438 \u0442.\u043f.<\/p>\n<p>  <\/p>\n<p>\u0418\u0442\u0430\u043a, \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0435\u0449\u0451 \u043e\u0434\u043d\u0430 \u0440\u0443\u0447\u043a\u0430. \u0414\u0435\u043b\u043e \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u044b (<code>Login<\/code> \/ <code>RegisterNewUser<\/code>) \u0432\u043d\u0435\u0448\u043d\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043d\u0435 \u0441\u0438\u043b\u044c\u043d\u043e \u043d\u0443\u0436\u043d\u044b \u043f\u0440\u0438 \u043d\u0430\u0448\u0435\u0439 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0445\u0435\u043c\u0435. \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c (\u0438\u043b\u0438 \u0435\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435) \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u0434\u0451\u0442 \u0432 SSO \u0437\u0430 \u0442\u043e\u043a\u0435\u043d\u043e\u043c, \u0438 \u0441 \u044d\u0442\u0438\u043c \u0442\u043e\u043a\u0435\u043d\u043e\u043c \u0443\u0436\u0435 \u0438\u0434\u0435\u0442 \u0432 URL Shortener, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043a\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0442\u043e\u043a\u0435\u043d\u0430, \u0431\u0435\u0437 \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 SSO \u0441\u043e \u0441\u0432\u043e\u0435\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b.<\/p>\n<p>  <\/p>\n<h3 id=\"grpc-metod-isadmin\">gRPC-\u043c\u0435\u0442\u043e\u0434: IsAdmin()<\/h3>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0435\u0440\u0435\u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u044e, \u044f \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0443\u044e, \u043d\u043e \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u0443\u044e, \u043d\u043e\u0432\u0443\u044e \u0440\u0443\u0447\u043a\u0443: <code>sso.IsAdmin(userID)<\/code>. \u041e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0430\u0442\u044c, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0430\u0434\u043c\u0438\u043d\u043e\u043c. Shortener \u0436\u0435 \u0441 \u0435\u0451 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0448\u0430\u0442\u044c \u2014 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0442\u044c \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0447\u0443\u0436\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u0438.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u043a\u0430\u043a\u0438\u0435 \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f \u043c\u044b \u0437\u0434\u0435\u0441\u044c \u043f\u043e\u0439\u0434\u0435\u043c:<\/p>\n<p>  <\/p>\n<p><strong>1. \u0420\u0443\u0447\u043a\u0430 \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u0430<\/strong>: \u0442\u043e \u0435\u0441\u0442\u044c, \u043b\u044e\u0431\u043e\u0439 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0439 \u0441\u043c\u043e\u0436\u0435\u0442 \u0443\u0437\u043d\u0430\u0442\u044c, \u043a\u0442\u043e \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u043c\u0438\u043d\u043e\u043c.<\/p>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:<\/p>\n<p>  <\/p>\n<ul>\n<li>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u0443\u044e (\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e) \u0441\u0435\u0442\u044c: \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435 \u0441\u043b\u043e\u0436\u043d\u043e, \u0438 \u0442\u043e\u0442 \u0436\u0435 Selectel (\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043b\u0435\u0435) \u0443\u043c\u0435\u0435\u0442 \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c &#171;\u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438&#187;:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/4r\/9j\/ua\/4r9jua0nvylnpwxuprsruydna7w.png\" alt=\"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438\" data-src=\"https:\/\/habrastorage.org\/webt\/4r\/9j\/ua\/4r9jua0nvylnpwxuprsruydna7w.png\"\/><\/p>\n<p>  <\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0432 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 JWT-\u0442\u043e\u043a\u0435\u043d. \u042d\u0442\u043e\u0442 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043f\u043e\u0434\u043e\u0439\u0434\u0451\u0442, \u0435\u0441\u043b\u0438 \u043f\u043e \u043a\u0430\u043a\u0438\u043c-\u0442\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c \u043d\u0435 \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0441\u0435\u0442\u044c.<\/p>\n<p>  <\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0412 \u043b\u044e\u0431\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043e\u0431\u0430 \u044d\u0442\u0438\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0432\u044b\u0445\u043e\u0434\u044f\u0442 \u0437\u0430 \u0440\u0430\u043c\u043a\u0438 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \u0412\u0435\u0440\u043e\u044f\u0442\u043d\u043e, \u0432 \u0431\u0443\u0434\u0443\u0449\u0438\u0445 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 \u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0443 \u043e\u0434\u0438\u043d \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432, \u043b\u0438\u0431\u043e \u0434\u0430\u0436\u0435 \u043e\u0431\u0430 (\u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445, \u0435\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e).<\/p>\n<p>  <\/p>\n<p><strong>2. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u0430\u0434\u043c\u0438\u043d\u043e\u043c \u0441\u0440\u0430\u0437\u0443 \u0432\u043e \u0432\u0441\u0435\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445<\/strong>: \u043a\u0430\u043a \u044f \u043f\u0438\u0441\u0430\u043b \u0432 \u0441\u0430\u043c\u043e\u043c \u043d\u0430\u0447\u0430\u043b\u0435, \u043c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043a\u0430 \u0437\u0430\u043c\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 \u0440\u043e\u043b\u0435\u0439, \u043e\u0441\u0442\u0430\u0432\u0438\u043c \u044d\u0442\u043e \u043d\u0430 \u0431\u0443\u0434\u0443\u0449\u0438\u0435 \u0433\u0430\u0439\u0434\u044b \ud83d\ude42<\/p>\n<p>  <\/p>\n<p><strong>3. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u043e userID \u0432\u0435\u0441\u044c JWT-\u0442\u043e\u043a\u0435\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f<\/strong>: \u044d\u0442\u043e \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u0442 \u043b\u044e\u0431\u043e\u043f\u044b\u0442\u043d\u044b\u043c \u043b\u0438\u0446\u0430\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u2014 \u0438\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0440\u0430\u0441\u0442\u044c \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 JWT-\u0442\u043e\u043a\u0435\u043d, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0439 <code>userID<\/code>. \u042d\u0442\u043e \u043d\u0435 \u0441\u0430\u043c\u044b\u0439 \u043b\u0443\u0447\u0448\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442, \u043d\u043e \u0437\u0430\u0442\u043e \u043e\u043d \u0441\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e\u0439. \u041c\u043e\u0436\u0435\u0442\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0442\u0430\u043a\u0436\u0435 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u0412 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u043c \u043f\u043b\u0430\u043d\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0442\u043e\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u043e\u0435: \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043a\u043e\u043b\u043e\u043d\u043a\u0443 <code>is_admin<\/code> \u0432 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 <code>users<\/code>. \u042d\u0442\u043e \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0440\u043e\u0448\u043e, \u0442.\u043a. \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0442\u043e\u0438\u0442 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%9D%D0%BE%D1%80%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%84%D0%BE%D1%80%D0%BC%D0%B0\" rel=\"nofollow noopener noreferrer\">\u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c<\/a> \u2014 \u0430 \u0438\u043c\u0435\u043d\u043d\u043e, \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0440\u043e\u043b\u0438 \/ \u043f\u0440\u0430\u0432\u0430 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443. \u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435, \u0447\u0442\u043e \u0443 \u0432\u0430\u0441 \u043c\u0438\u043b\u043b\u0438\u043e\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0430 \u0430\u0434\u043c\u0438\u043d\u043e\u0432 \u0432\u0441\u0435\u0433\u043e 10 \u2014 \u0432\u0430\u043c \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043c\u0438\u043b\u043b\u0438\u043e\u043d \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 <code>is_admin<\/code>, \u0445\u043e\u0442\u044f \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u0441\u0435\u0433\u043e 10 \u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0435. \u042f \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0432\u0430\u043c \u0441\u0440\u0430\u0437\u0443 \u0436\u0435 \u043f\u0435\u0440\u0435\u0434\u0435\u043b\u0430\u0442\u044c \u043c\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043d\u0430 \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u2014 \u044d\u0442\u043e \u0438 \u0445\u043e\u0440\u043e\u0448\u0435\u0435 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u0435, \u0438 \u0445\u043e\u0440\u043e\u0448\u0430\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442. \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043d\u0430\u0448 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 <code>protos<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">service Auth {   \/\/ ...   \/\/ IsAdmin checks whether a user is an admin.   rpc IsAdmin (IsAdminRequest) returns (IsAdminResponse); }  message IsAdminRequest {   int64 user_id = 1; \/\/ User ID to validate. }  message IsAdminResponse {   bool is_admin = 1;  \/\/ Indicates whether the user is an admin. }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043f\u0435\u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c go-\u0444\u0430\u0439\u043b\u044b \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 <code>task generate<\/code> (\u0441\u043c. \u0440\u0430\u0437\u0434\u0435\u043b \u043f\u0440\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u0430), \u0437\u0430\u043f\u0443\u0448\u0438\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0438 \u043d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u0435\u0439 \u043d\u043e\u0432\u044b\u0439 git-\u0442\u0435\u0433.<\/p>\n<p>  <\/p>\n<p>\u0417\u0430\u0442\u0435\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044e \u0432 SSO:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">go get github.com\/GolangLessons\/protos<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0430\u043a\u0435\u0442\u044b <a href=\"https:\/\/github.com\/grpc-ecosystem\/go-grpc-middleware\" rel=\"nofollow noopener noreferrer\">go-grpc-middleware<\/a> \u0438 \u0441\u0430\u043c <a href=\"http:\/\/google.golang.org\/grpc\" rel=\"nofollow noopener noreferrer\">grpc-go<\/a>, \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u041d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043b\u043e\u043d\u043a\u0438 <code>is_admin<\/code><\/li>\n<li>\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>IsAdmin()<\/code> \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439<\/li>\n<li>\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>IsAdmin()<\/code> \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 <code>Auth<\/code> (\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439)<\/li>\n<li>\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c gRPC-\u043c\u0435\u0442\u043e\u0434 <code>IsAdmin()<\/code><\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0423\u0444\u2026 \u0427\u0442\u043e \u043f\u043e\u0434\u0435\u043b\u0430\u0442\u044c, \u0442\u0430\u043a\u043e\u0432\u044b \u0440\u0435\u0430\u043b\u0438\u0438 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \ud83d\ude42 \u042d\u0442\u043e \u0435\u0449\u0435 \u043d\u0435 \u0441\u0430\u043c\u043e\u0435 \u0441\u0442\u0440\u0430\u0448\u043d\u043e\u0435, \u043f\u043e\u0440\u043e\u0439 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c \u043c\u0435\u0436\u0434\u0443 \u043a\u0430\u0436\u0434\u044b\u043c \u0441\u043b\u043e\u0435\u043c. \u041e\u0442 \u0447\u0430\u0441\u0442\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0440\u0435\u0448\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>int64<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 <code>userID<\/code> \u0432\u043c\u0435\u0441\u0442\u043e \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 (\u0445\u043e\u0442\u044f, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0431\u044b\u043b\u043e \u0431\u044b \u043b\u0443\u0447\u0448\u0435).<\/p>\n<p>  <\/p>\n<p>\u0418\u0434\u0451\u043c \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443, \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"sql\">-- migrations\/2_add_is_admin_column_to_users_tbl.up.sql ALTER TABLE users     ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT FALSE;<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u043e\u0431\u0440\u0430\u0442\u043d\u0430\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"sql\">-- migrations\/2_add_is_admin_column_to_users_tbl.down.sql ALTER TABLE users DROP COLUMN is_admin;<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043c\u0438\u0433\u0440\u0430\u0442\u043e\u0440:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go run .\/cmd\/migrator --storage-path=.\/storage\/sso.db --migrations-path=.\/migrations<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/storage\/sqlite\/sqlite.go  func (s *Storage) IsAdmin(ctx context.Context, userID int64) (bool, error) {     const op = \"storage.sqlite.IsAdmin\"      stmt, err := s.db.Prepare(\"SELECT is_admin FROM users WHERE id = ?\")     if err != nil {         return false, fmt.Errorf(\"%s: %w\", op, err)     }      row := stmt.QueryRowContext(ctx, userID)      var isAdmin bool      err = row.Scan(&amp;isAdmin)     if err != nil {         if errors.Is(err, sql.ErrNoRows) {             return false, fmt.Errorf(\"%s: %w\", op, storage.ErrUserNotFound)         }          return false, fmt.Errorf(\"%s: %w\", op, err)     }      return isAdmin, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 <code>Auth<\/code> (\u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>UserProvider<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/services\/auth\/auth.go  type UserProvider interface {     \/\/ ...     IsAdmin(ctx context.Context, userID int64) (bool, error) }  \/\/ IsAdmin checks if user is admin. func (a *Auth) IsAdmin(ctx context.Context, userID int64) (bool, error) {     const op = \"Auth.IsAdmin\"      log := a.log.With(         slog.String(\"op\", op),         slog.Int64(\"user_id\", userID),     )      log.Info(\"checking if user is admin\")      isAdmin, err := a.usrProvider.IsAdmin(ctx, userID)     if err != nil {         return false, fmt.Errorf(\"%s: %w\", op, err)     }      log.Info(\"checked if user is admin\", slog.Bool(\"is_admin\", isAdmin))      return isAdmin, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u043d\u0435 \u0437\u0430\u0431\u044b\u0432 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>Auth<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/grpc\/auth\/server.go  type Auth interface {     \/\/ ...     IsAdmin(ctx context.Context, userID int64) (bool, error) }  func (s *serverAPI) IsAdmin(     ctx context.Context,     in *ssov1.IsAdminRequest, ) (*ssov1.IsAdminResponse, error) {     if in.UserId == 0 {         return nil, status.Error(codes.InvalidArgument, \"user_id is required\")     }      isAdmin, err := s.auth.IsAdmin(ctx, in.GetUserId())     if err != nil {         if errors.Is(err, storage.ErrUserNotFound) {             return nil, status.Error(codes.NotFound, \"user not found\")         }          return nil, status.Error(codes.Internal, \"failed to check admin status\")     }      return &amp;ssov1.IsAdminResponse{IsAdmin: isAdmin}, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0443\u0447\u043a\u0443 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u044f \u0442\u0443\u0442 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u043d\u043e \u043e\u043d\u0430 \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e, \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438. \u042d\u0442\u043e \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0435 \u0445\u043e\u0440\u043e\u0448\u0435\u0435 \u0443\u043f\u0440\u0430\u0436\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0438. \u0410 \u043f\u043e\u043a\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0437\u0430\u0434\u0430\u0442\u044c \u0444\u043b\u0430\u0433 <code>is_admin<\/code> \u0432 \u0411\u0414 \u0432\u0440\u0443\u0447\u043d\u0443\u044e.<\/p>\n<p>  <\/p>\n<h3 id=\"sozdanie-klienta-v-storonnem-servise\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435<\/h3>\n<p>  <\/p>\n<p>\u0417\u0430\u0445\u043e\u0434\u0438\u043c \u0432 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442 (\u0432 \u043c\u043e\u0451\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 <a href=\"https:\/\/github.com\/GolangLessons\/url-shortener\" rel=\"nofollow noopener noreferrer\">URL Shortener<\/a>) \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e <code>protos<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">go get github.com\/GolangLessons\/protos<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f SSO. \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0443\u0432\u0438\u0434\u0438\u043c \u0432\u0441\u044e \u043c\u0430\u0433\u0438\u044e \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 gRPC \u2014 \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0451 \u0443\u0436\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e \u0437\u0430 \u043d\u0430\u0441, \u043d\u0430\u043c \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043b\u0438\u0448\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/clients\/sso\/grpc\/grpc.go package grpc  import (     \"context\"     \"fmt\"     \"log\/slog\"     \"time\"      ssov1 \"github.com\/GolangLessons\/protos\/gen\/go\/sso\"     grpclog \"github.com\/grpc-ecosystem\/go-grpc-middleware\/v2\/interceptors\/logging\"     grpcretry \"github.com\/grpc-ecosystem\/go-grpc-middleware\/v2\/interceptors\/retry\"     \"google.golang.org\/grpc\"     \"google.golang.org\/grpc\/codes\"     \"google.golang.org\/grpc\/credentials\/insecure\" )  type Client struct {     api ssov1.AuthClient     log *slog.Logger }  func New(     ctx context.Context,     log *slog.Logger,     addr string,            \/\/ \u0410\u0434\u0440\u0435\u0441 SSO-\u0441\u0435\u0440\u0432\u0435\u0440\u0430     timeout time.Duration,  \/\/ \u0422\u0430\u0439\u043c\u0430\u0443\u0442 \u043d\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u043e\u043f\u044b\u0442\u043a\u0438     retriesCount int,       \/\/ \u041a\u043e\u043b\u0438\u0447\u0435\u0442\u0441\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 ) (*Client, error) {     const op = \"grpc.New\"      \/\/ \u041e\u043f\u0446\u0438\u0438 \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u0430 grpcretry     retryOpts := []grpcretry.CallOption{         grpcretry.WithCodes(codes.NotFound, codes.Aborted, codes.DeadlineExceeded),         grpcretry.WithMax(uint(retriesCount)),         grpcretry.WithPerRetryTimeout(timeout),     }      \/\/ \u041e\u043f\u0446\u0438\u0438 \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u0430 grpclog     logOpts := []grpclog.Option{         grpclog.WithLogOnEvents(grpclog.PayloadReceived, grpclog.PayloadSent),     }      \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 gRPC-\u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c SSO \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430     cc, err := grpc.DialContext(ctx, addr,         grpc.WithTransportCredentials(insecure.NewCredentials()),         grpc.WithChainUnaryInterceptor(             grpclog.UnaryClientInterceptor(InterceptorLogger(log), logOpts...),             grpcretry.UnaryClientInterceptor(retryOpts...),         ))     if err != nil {         return nil, fmt.Errorf(\"%s: %w\", op, err)     }      \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c gRPC-\u043a\u043b\u0438\u0435\u043d\u0442 SSO\/Auth     grpcClient := ssov1.NewAuthClient(cc)      return &amp;Client{         api: grpcClient,     }, nil }  \/\/ InterceptorLogger adapts slog logger to interceptor logger. \/\/ This code is simple enough to be copied and not imported. func InterceptorLogger(l *slog.Logger) grpclog.Logger {     return grpclog.LoggerFunc(func(ctx context.Context, lvl grpclog.Level, msg string, fields ...any) {         l.Log(ctx, slog.Level(lvl), msg, fields...)     }) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u0430\u043c\u0430\u044f \u0433\u043b\u0430\u0432\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u0447\u043a\u0430 \u0437\u0434\u0435\u0441\u044c: <code>grpcClient := ssov1.NewAuthClient(cc)<\/code>. \u041e\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 gRPC \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 \u044d\u0442\u043e SSO, \u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u2014 Auth. \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u043c\u044b \u0432\u043d\u0443\u0442\u0440\u0438 proto-\u0444\u0430\u0439\u043b\u0430 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0443 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b: Auth \u0438 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c Permissions, UserInfo \u0438 \u0442.\u043f.<\/p>\n<p>  <\/p>\n<p>\u041a \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0434\u0432\u0430 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u0430 \u2014 <code>grpclog<\/code> \u0438 <code>grpcretry<\/code>. \u041f\u0435\u0440\u0432\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043b\u043e \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u043e\u0442\u0432\u0435\u0442\u0430, \u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0435\u0442\u0440\u0430\u0438 \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0435\u0443\u0434\u0430\u0447\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432.<\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043b\u0438\u0448\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>IsAdmin()<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/clients\/sso\/grpc\/grpc.go \/\/ ...  func (c *Client) IsAdmin(ctx context.Context, userID int64) (bool, error) {     const op = \"grpc.IsAdmin\"      resp, err := c.api.IsAdmin(ctx, &amp;ssov1.IsAdminRequest{         UserId: userID,     })     if err != nil {         return false, fmt.Errorf(\"%s: %w\", op, err)     }      return resp.IsAdmin, nil }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0435\u043b\u0435\u0441\u0442\u044c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 gRPC \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0441\u0430\u043c API-\u043a\u043b\u0438\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0443\u0436\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u0437\u0430 \u043d\u0430\u0441, \u043a\u0430\u043a \u0438 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b. \u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e, \u043c\u043e\u0434\u0435\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 (<code>ssov1.LoginRequest<\/code> \u0438 \u0434\u0440.) \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \/ \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0442\u043e\u0436\u0435 \u0443\u0436\u0435 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b, \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c, \u0442\u0430\u043a \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c.<\/p>\n<p>  <\/p>\n<h3 id=\"otpravka-zaprosa-iz-storonnego-servisa\">\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438\u0437 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430<\/h3>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 URL Shortener \u2014 \u044d\u0442\u043e REST API \u0441\u0435\u0440\u0432\u0438\u0441, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 middleware (\u0430\u043d\u0430\u043b\u043e\u0433 \u0438\u043d\u0442\u0435\u0440\u0441\u0435\u043f\u0442\u043e\u0440\u043e\u0432 gRPC).<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0435\u0433\u043e \u0437\u0434\u0435\u0441\u044c: <code>internal\/http-server\/middleware\/auth\/auth.go<\/code><\/p>\n<p>  <\/p>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, JWT-\u0442\u043e\u043a\u0435\u043d \u043e\u0431\u044b\u0447\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u0432\u0438\u0434\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">Authorization: \"Bearer &lt;jwt_token>\"<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u043d\u0430\u043f\u0438\u0448\u0435\u043c <\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/middleware\/auth\/auth.go package auth  import \"strings\"  \/\/ \/\/ extractBearerToken extracts auth token from Authorization header. func extractBearerToken(r *http.Request) string {     authHeader := r.Header.Get(\"Authorization\")     splitToken := strings.Split(authHeader, \"Bearer \")     if len(splitToken) != 2 {         return \"\"     }      return splitToken[1] }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u0430\u043c middleware:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/middleware\/auth\/auth.go  package auth  import (     \"context\"     \"errors\"     \"log\/slog\"     \"net\/http\"     \"strings\"      \"url-shortener\/internal\/lib\/jwt\"     \"url-shortener\/internal\/lib\/logger\/sl\" )  var (     ErrInvalidToken = errors.New(\"invalid token\")     ErrFailedIsAdminCheck = errors.New(\"failed to check if user is admin\") )  \/\/ New creates new auth middleware. func New(     log *slog.Logger,     appSecret string,     permProvider PermissionProvider, ) func(next http.Handler) http.Handler {     const op = \"middleware.auth.New\"      log = log.With(slog.String(\"op\", op))      \/\/ \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a     return func(next http.Handler) http.Handler {         return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {             \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c JWT-\u0442\u043e\u043a\u0435\u043d \u0438\u0437 \u0437\u0430\u043f\u0440\u043e\u0441\u0430             tokenStr := extractBearerToken(r)             if tokenStr == \"\" {                 \/\/ It's ok, if user is not authorized                 next.ServeHTTP(w, r)                 return             }              \/\/ \u041f\u0430\u0440\u0441\u0438\u043c \u0438 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u0442\u043e\u043a\u0435\u043d, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0430\u044f appSecret             claims, err := jwt.Parse(tokenStr, appSecret)             if err != nil {                 log.Warn(\"failed to parse token\", sl.Err(err))                  \/\/ But if token is invalid, we shouldn't handle request                 ctx := context.WithValue(r.Context(), errorKey, ErrInvalidToken)                 next.ServeHTTP(w, r.WithContext(ctx))                  return             }              log.Info(\"user authorized\", slog.Any(\"claims\", claims))              \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0430\u0434\u043c\u0438\u043d\u043e\u0432             isAdmin, err := permProvider.IsAdmin(r.Context(), claims.UID)             if err != nil {                 log.Error(\"failed to check if user is admin\", sl.Err(err))                  ctx := context.WithValue(r.Context(), errorKey, ErrFailedIsAdminCheck)                 next.ServeHTTP(w, r.WithContext(ctx))                  return             }              \/\/ \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442,             \/\/ \u043e\u0442\u043a\u0443\u0434\u0430 \u0435\u0433\u043e \u0441\u043c\u043e\u0433\u0443\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b.             ctx := context.WithValue(r.Context(), uidKey, claims.UID)             ctx = context.WithValue(r.Context(), isAdminKey, isAdmin)              next.ServeHTTP(w, r.WithContext(ctx))         })     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u044f \u043f\u0438\u0448\u0443 \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u043a\u043e\u0434\u0430, \u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0435 \u2014 \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \u0422\u0430\u043a \u0432\u043e\u0442, \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0435 \u044f \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0432\u0430\u043c \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0441\u0432\u043e\u0451\u043c \u043a\u043e\u0434\u0435, \u0442.\u043a. \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0442\u0443\u0442 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0442\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u0442\u0438\u043b\u0435 \u2014 &#171;\u0431\u0430\u0433 \u0438\u043b\u0438 \u0444\u0438\u0447\u0430?&#187; \ud83d\ude42<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438, \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0447\u0430\u0441\u0442\u044c, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0443\u044e \u0437\u0430 \u043f\u0430\u0440\u0441\u0438\u043d\u0433 JWT-\u0442\u043e\u043a\u0435\u043d\u0430, \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 \u0438 \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0430\u043c\u0438. \u042d\u0442\u043e \u0442\u043e\u0436\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0449\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0443 \u043d\u0430\u0441 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u041f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0438\u0437 \u0437\u0430\u043f\u0440\u043e\u0441\u0430<\/li>\n<li>\u0415\u0441\u043b\u0438 \u0435\u0433\u043e \u043d\u0435\u0442 \u2014 \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u0442\u0440\u0430\u0448\u043d\u043e\u0433\u043e, \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u044b \u0434\u043b\u044f \u043d\u0435\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439<\/li>\n<li>\u0415\u0441\u043b\u0438 \u0436\u0435 \u0442\u043e\u043a\u0435\u043d \u0435\u0441\u0442\u044c, \u043d\u043e \u0432 \u043d\u0451\u043c \u0438\u043c\u0435\u044e\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u0438, \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0438 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u043e \u0446\u0435\u043f\u043e\u0447\u043a\u0435 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u044b<\/li>\n<li>\u0415\u0441\u043b\u0438 \u0436\u0435 \u0432\u0441\u0451 \u043e\u043a, \u0442\u043e \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u044f\u043d\u0435\u043c <code>userID<\/code> \u0438 <code>isAdmin<\/code> \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u043e\u0432<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0437\u0434\u0435\u0441\u044c \u0436\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u044b, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u044d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"go\">\/\/ internal\/http-server\/middleware\/auth\/auth.go \/\/ ...  func UIDFromContext(ctx context.Context) (int64, bool) {     uid, ok := ctx.Value(uidKey).(int64)     return uid, ok }  func ErrorFromContext(ctx context.Context) (error, bool) {     err, ok := ctx.Value(errorKey).(error)     return err, ok }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u044d\u0442\u043e\u0442 middleware \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043a HTTP-\u0440\u043e\u0443\u0442\u0435\u0440\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u0445\u044d\u043d\u0434\u043b\u0435\u0440\u0430\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u043e\u043d \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u0440\u043e\u043c, \u043b\u0438\u0431\u043e \u0430\u0434\u043c\u0438\u043d\u043e\u043c. \u042d\u0442\u043e \u0432\u044b\u0445\u043e\u0434\u0438\u0442 \u0437\u0430 \u0440\u0430\u043c\u043a\u0438 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438, \u043d\u043e \u044f \u0432\u0435\u0440\u044e \u0447\u0442\u043e \u0432\u044b \u0441\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u0441\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \ud83d\ude42<\/p>\n<p>  <a name=\"10\"><\/a>  <\/p>\n<h2 id=\"nastraivaem-avtomaticheskiy-deploy---github-actions\">\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u2014 GitHub Actions<\/h2>\n<p>  <\/p>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u0433\u043e\u0442\u043e\u0432, \u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u0435\u0433\u043e \u043d\u0430 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u2014 \u043c\u044b \u0432\u0435\u0434\u044c \u043d\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u0445\u043e\u0441\u0442\u0438\u0442\u044c 24\/7 \u043d\u0430 \u0441\u0432\u043e\u0451\u043c \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435 \ud83d\ude42<\/p>\n<p>  <\/p>\n<p>\u042f \u0447\u0435\u043b\u043e\u0432\u0435\u043a \u043b\u0435\u043d\u0438\u0432\u044b\u0439, \u0438 \u043c\u043d\u0435 \u043d\u0435 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0432\u043e\u0437\u0438\u0442\u044c\u0441\u044f \u0441 \u0434\u0435\u043f\u043b\u043e\u0435\u043c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0430\u0441\u0442\u0440\u043e\u044e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 <a href=\"https:\/\/github.com\/features\/actions\" rel=\"nofollow noopener noreferrer\">GitHub Actions<\/a>. \u041a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u043c\u0435\u043d\u044f\u0435\u043c \u0432 \u043a\u043e\u0434\u0435, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0431\u0443\u0434\u0435\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f. \u0411\u043b\u0430\u0433\u043e, GitHub Actions \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439, \u0438 \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u0439.<\/p>\n<p>  <\/p>\n<h3 id=\"arenda-oblachnogo-servera\">\u0410\u0440\u0435\u043d\u0434\u0430 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430<\/h3>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u0435\u0431\u0435 \u0441\u0435\u0440\u0432\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u043a\u0443\u0434\u0430 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c. \u042f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/selectel.ru\/services\/cloud\/sharedline\/?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=cloud_article_tgbot_230223_content\" rel=\"nofollow noopener noreferrer\">\u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043b\u0438\u043d\u0435\u0439\u043a\u0438 Shared Line<\/a> \u043e\u0442 Selectel. \u042d\u0442\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u043e\u0431\u043b\u0430\u043a\u0430 \u0438 \u043d\u0435 \u043f\u0435\u0440\u0435\u043f\u043b\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0437\u0430 \u043d\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b, \u0442\u0430\u043a \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u043e\u043f\u043b\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0430\u0441\u0442\u044c \u044f\u0434\u0440\u0430 \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, 10, 20 \u0438\u043b\u0438 50%<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c\u0441\u044f \u0432 \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 <strong>\u041e\u0431\u043b\u0430\u0447\u043d\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430<\/strong>. \u0417\u0430\u0442\u0435\u043c \u2014 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0435\u0433\u043e.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/h_\/ll\/eb\/h_lleb3ifeqelo39oqg5m62amdy.png\" alt=\"\u0421\u0435\u0440\u0432\u0438\u0441\u0443 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442 \u041e\u0421 Ubuntu 22.04 LTS, 2 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u044f\u0434\u0440\u0430 \u0441 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439 \u0433\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0432 20% \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438, 2 \u0413\u0411 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 10 \u0413\u0411 \u043d\u0430 \u0441\u0435\u0442\u0435\u0432\u043e\u043c \u0434\u0438\u0441\u043a\u0435 (\u0431\u0430\u0437\u043e\u0432\u044b\u0439 HDD).\" data-src=\"https:\/\/habrastorage.org\/webt\/h_\/ll\/eb\/h_lleb3ifeqelo39oqg5m62amdy.png\"\/><\/p>\n<p>  <\/p>\n<p><em>\u0421\u0435\u0440\u0432\u0438\u0441\u0443 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442 \u041e\u0421 Ubuntu 22.04 LTS, 2 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u044f\u0434\u0440\u0430 \u0441 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439 \u0433\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0432 20% \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438, 2 \u0413\u0411 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 10 \u0413\u0411 \u043d\u0430 \u0441\u0435\u0442\u0435\u0432\u043e\u043c \u0434\u0438\u0441\u043a\u0435 (\u0431\u0430\u0437\u043e\u0432\u044b\u0439 HDD).<\/em><\/p>\n<p>  <\/p>\n<p>\u0412 \u043f\u043e\u043b\u0435 Name \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0438\u043c\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0421\u043e\u0432\u0435\u0442\u0443\u044e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e\u0435, \u0438\u043d\u0430\u0447\u0435 \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043f\u0443\u0442\u0430\u0442\u044c\u0441\u044f, \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0431\u043e\u043b\u044c\u0448\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430.<\/p>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u0436\u0435 \u0441\u0440\u0430\u0437\u0443 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c SSH Key \u2014 \u043e\u043d \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f. \u041f\u0430\u0440\u043e\u043b\u044c \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f, \u043d\u043e \u043c\u043e\u0436\u0435\u0442\u0435 \u0435\u0433\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041a\u043e\u0440\u043e\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u043b\u0438\u043a\u0431\u0435\u0437 \u043f\u043e SSH-\u043a\u043b\u044e\u0447\u0430\u043c<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0432 \u044d\u0442\u043e\u043c \u043d\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0435\u0441\u044c, \u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435. \u041d\u043e \u043f\u043e\u043a\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0437\u043d\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u043d\u0430\u043c \u043d\u0430\u0434\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 \u2014 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u0443 \u0441\u0435\u0431\u044f \u043d\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 Mac \u0438\u043b\u0438 Linux, \u0442\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 (\u0441 Windows \u044f \u043d\u0435 \u0434\u0440\u0443\u0436\u0443, \u0442\u0443\u0442 \u0432\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043f\u043e\u0433\u0443\u0433\u043b\u0438\u0442\u044c \u0438\u043b\u0438 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0443):<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ssh-keygen -t rsa -b 4096 -C \"some comment\"<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0437\u0430\u0434\u0430\u0441\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u0432, \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0441\u0435 \u0438\u0445 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044f \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. \u0418\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430 \u043a\u043b\u044e\u0447\u0430 \u0438 \u043f\u0443\u0442\u044c \u0434\u043e \u043d\u0435\u0433\u043e.<\/p>\n<p>  <\/p>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435, \u0432\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 \u0434\u0432\u0430 \u0444\u0430\u0439\u043b\u0430: <\/p>\n<p>  <\/p>\n<ul>\n<li><code>&lt;path>\/&lt;key_name><\/code> \u2014 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 \u043a\u043b\u044e\u0447<\/li>\n<li><code>&lt;path>\/&lt;key_name>.pub<\/code> \u2014 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u2014 \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u0438 \u0435\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 (\u0441\u043c. \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u044b \u0432\u044b\u0448\u0435). \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f GitHub Actions, \u0442\u0443\u0434\u0430 \u043c\u044b \u0435\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435.<\/p>\n<\/div><\/div>\n<p>  <\/p>\n<h3 id=\"nastroyka-github-workflow\">\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 GitHub Workflow<\/h3>\n<p>  <\/p>\n<p>\u0418\u0442\u0430\u043a, GitHub Actions. \u042d\u0442\u043e \u0441\u0435\u0440\u0432\u0438\u0441 \u0432\u043d\u0443\u0442\u0440\u0438 \u0433\u0438\u0442\u0445\u0430\u0431\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 Workflow \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0430\u043c \u0445\u043e\u0441\u0442\u044f\u0442\u0441\u044f \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u0435\u043f\u043b\u043e\u0439 \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u044b, \u043f\u0440\u043e\u0433\u043e\u043d \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043c\u043d\u043e\u0433\u043e\u0435 \u0434\u0440\u0443\u0433\u043e\u0435. \u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f, \u043a\u0430\u043a \u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c.\u00a0<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f Workflow \u043a \u0441\u0432\u043e\u0435\u043c\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c yaml \u0444\u0430\u0439\u043b \u0441 \u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439 \u0432 \u043f\u0430\u043f\u043a\u0443: <code>.github\/workflows<\/code> \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041d\u0430\u0437\u043e\u0432\u0451\u043c \u043d\u0430\u0448 \u0444\u0430\u0439\u043b <code>deploy.yaml<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u0442\u0440\u0435\u0445 \u043e\u0431\u0449\u0438\u0445 \u0441\u0435\u043a\u0446\u0438\u0439:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>name<\/code> \u2014 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 workflow, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 Actions,<\/li>\n<li><code>on<\/code> \u2014 \u0443\u0441\u043b\u043e\u0432\u0438\u044f, \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f workflow,<\/li>\n<li><code>jobs<\/code> \u2014 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0434\u0435\u043b\u0430\u0442\u044c.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u043f\u0435\u0440\u0432\u044b\u0445 \u0434\u0432\u0443\u0445, \u043e\u043d\u0438 \u0441\u0430\u043c\u044b\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\"># .github\/workflows\/deploy.yaml  name: Deploy App # \u0414\u0430\u0435\u043c \u043e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f  on:   workflow_dispatch: # \u0420\u0443\u0447\u043d\u043e\u0439 \u0437\u0430\u043f\u0443\u0441\u043a     inputs: # \u0427\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u0432\u0435\u0441\u0442\u0438 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435       tag: # \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0442\u0435\u0433 \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f         description: 'Tag to deploy'         required: true<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 workflow \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0440\u0443\u0447\u043d\u0443\u044e. \u041f\u0440\u0438\u0442\u043e\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c git \u0442\u0435\u0433, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u043c \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441. \u041c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0449\u0435 \u2014 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u0443\u0448\u0435 \/ \u043c\u0435\u0440\u0436\u0435 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0432\u0435\u0442\u043a\u0443, \u043d\u043e \u043c\u043d\u0435 \u0442\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f, \u044f \u0445\u043e\u0447\u0443 \u0441\u0430\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e \u0434\u0435\u043b\u043e.<\/p>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0438\u0434\u0435\u0442 \u0441\u0435\u043a\u0446\u0438\u044f <code>jobs<\/code>. \u041e\u043d\u0430 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0441\u0435\u043a\u0446\u0438\u0439 \u2014 <code>deploy<\/code> \u0438 <code>steps<\/code>. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u2014 <code>deploy<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\"># .github\/workflows\/deploy.yaml  # name: ..., on: ...  jobs:   deploy:     runs-on: ubuntu-latest # \u041e\u0421 \u0434\u043b\u044f runner'\u0430     env: # \u0412\u0432\u043e\u0434\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043b\u0435\u0435       HOST: root@&lt;your_ip> # \u043b\u043e\u0433\u0438\u043d \/ \u0445\u043e\u0441\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0435\u043f\u043b\u043e\u0438\u043c       DEPLOY_DIRECTORY: \/root\/apps\/grpc-auth # \u0432 \u043a\u0430\u043a\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0434\u0435\u043f\u043b\u043e\u0438\u043c       CONFIG_PATH: \/root\/apps\/grpc-auth\/config\/prod.yaml # \u043a\u043e\u043d\u0444\u0438\u0433 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435         ENV_FILE_PATH: \/root\/apps\/grpc-auth\/config.env # env-\u0444\u0430\u0439\u043b \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0438\u0434\u0435\u0442 \u0441\u0435\u043a\u0446\u0438\u044f <code>steps<\/code> \u2014 \u043e\u043d\u0430 \u0441\u0430\u043c\u0430\u044f \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0438 \u0437\u0430\u0431\u043e\u0440\u0438\u0441\u0442\u0430\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\"># .github\/workflows\/deploy.yaml  # name: ..., on: ...  jobs:     # deploy: ...      steps:       - name: Checkout repository         uses: actions\/checkout@v2         with:           ref: ${{ github.event.inputs.tag }}       - name: Check if tag exists         run: |           git fetch --all --tags           if ! git tag | grep -q \"^${{ github.event.inputs.tag }}$\"; then             echo \"error: Tag '${{ github.event.inputs.tag }}' not found\"             exit 1           fi       - name: Set up Go         uses: actions\/setup-go@v2         with:           go-version: 1.21.2       - name: Build app         run: |           go mod download           go build -o grpc-auth .\/cmd\/sso       - name: Deploy to VM         run: |           sudo apt-get install -y ssh rsync           echo \"$DEPLOY_SSH_KEY\" > deploy_key.pem           chmod 600 deploy_key.pem           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"mkdir -p ${{ env.DEPLOY_DIRECTORY }}\"           rsync -avz -e 'ssh -i deploy_key.pem -o StrictHostKeyChecking=no' --exclude='.git' .\/ ${{ env.HOST }}:${{ env.DEPLOY_DIRECTORY }}         env:           DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}       - name: Remove old systemd service file         run: |           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"rm -f \/etc\/systemd\/system\/grpc-auth.service\"       - name: List workspace contents         run: |           echo \"Listing deployment folder contents:\"           ls -la ${{ github.workspace }}\/deployment       - name: Create environment file on server         run: |           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"touch ${{ env.ENV_FILE_PATH }}\"           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"chmod 600 ${{ env.ENV_FILE_PATH }}\"           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"echo 'CONFIG_PATH=${{ env.CONFIG_PATH }}' > ${{ env.ENV_FILE_PATH }}\"           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"echo 'HTTP_SERVER_PASSWORD=${{ secrets.AUTH_PASS }}' >> ${{ env.ENV_FILE_PATH }}\"       - name: Copy systemd service file         run: |           scp -i deploy_key.pem -o StrictHostKeyChecking=no ${{ github.workspace }}\/deployment\/grpc-auth.service ${{ env.HOST }}:\/tmp\/grpc-auth.service           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"mv \/tmp\/grpc-auth.service \/etc\/systemd\/system\/grpc-auth.service\"       - name: Start application         run: |           ssh -i deploy_key.pem -o StrictHostKeyChecking=no ${{ env.HOST }} \"systemctl daemon-reload &amp;&amp; systemctl restart grpc-auth.service\"<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 <code>steps<\/code> \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443. \u041a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 (\u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435):<\/p>\n<p>  <\/p>\n<ul>\n<li><code>name<\/code> \u2014 \u0438\u043c\u044f \u0448\u0430\u0433\u0430, \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f workflow, \u043f\u0438\u0448\u0435\u043c \u0447\u0442\u043e-\u0442\u043e \u043e\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e\u0435;<\/li>\n<li><code>uses<\/code> \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u043d\u0435\u0448\u043d\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>uses: actions\/setup-go@v2<\/code> \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0448\u0430\u0433 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 setup-go, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0435 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 actions \u043d\u0430 GitHub;<\/li>\n<li><code>with<\/code> \u2014 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435;<\/li>\n<li><code>env<\/code> \u2014 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0448\u0430\u0433\u0430;<\/li>\n<li><code>run<\/code> \u2014 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u0430\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0430.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u0448\u0430\u0433:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>Checkout repository<\/code>: \u043a\u043b\u043e\u043d\u0438\u0440\u0443\u0435\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0432 runner<\/li>\n<li><code>Check if tag exists<\/code>: \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u0433<\/li>\n<li><code>Set up Go<\/code>: \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Go<\/li>\n<li><code>Build app<\/code>: \u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/li>\n<li><code>Deploy to VM<\/code>: \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u043d\u0430 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u043c\u0430\u0448\u0438\u043d\u0443.<\/li>\n<li><code>Remove old systemd service file<\/code>: \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u0442\u0430\u0440\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u0435\u0440\u0432\u0438\u0441\u0430 systemd \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/li>\n<li><code>List workspace contents<\/code>: \u0412\u044b\u0432\u043e\u0434\u0438\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u043d\u0430 runner.<\/li>\n<li><code>Create environment file on server<\/code>: \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/li>\n<li><code>Copy systemd service file<\/code>: \u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u0439\u043b \u0441\u0435\u0440\u0432\u0438\u0441\u0430 systemd \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440.<\/li>\n<li><code>Start application<\/code>: \u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u0442\u0435, \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u0440\u043e\u0434\u0435\u043b\u044b\u0432\u0430\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0441 \u0444\u0430\u0439\u043b\u043e\u043c \u043a\u043e\u043d\u0444\u0438\u0433\u0430 <code>systemd<\/code>, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u044d\u0442\u0443 \u0441\u043b\u0443\u0436\u0431\u0443. \u042d\u0442\u043e \u043d\u0430\u0434\u0435\u0436\u043d\u0435\u0439, \u0447\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0433\u043e \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0435\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443\u043f\u0430\u0434\u0435\u0442, <code>systemd<\/code> \u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u2014 <code>deployment\/grpc-auth.service<\/code>. \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0444\u0430\u0439\u043b\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">[Unit] Description=gRPC Auth After=network.target  [Service] User=root WorkingDirectory=\/root\/apps\/grpc-auth ExecStart=\/root\/apps\/grpc-auth\/grpc-auth Restart=always RestartSec=4 StandardOutput=inherit EnvironmentFile=\/root\/apps\/grpc-auth\/config.env  [Install] WantedBy=multi-user.target<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432 workflow \u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u0444\u0430\u0439\u043b <code>prod.yaml<\/code>. \u042d\u0442\u043e \u043a\u043e\u043d\u0444\u0438\u0433, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u043e\u043d \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e.\u00a0\u0410 \u0438\u043c\u0435\u043d\u043d\u043e, \u044f \u0443\u043a\u0430\u0437\u0430\u043b \u0432 \u043d\u0451\u043c \u0434\u0440\u0443\u0433\u043e\u0439 <code>storage_path<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\"># config\/prod.yaml  env: \"prod\" storage_path: \"\/root\/apps\/storage\/sso.db\" grpc:   port: 44044   timeout: 5s migrations_path: \".\/migrations\" <\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443, \u0443 \u043d\u0430\u0441 \u043f\u043e\u043a\u0430 \u043d\u0435\u0442 RPC-\u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 <code>apps<\/code>, \u0437\u0430\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0432\u0440\u0443\u0447\u043d\u0443\u044e. \u041d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442, \u0441\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u2014 \u0432\u0437\u044f\u0442\u044c \u0432\u0430\u0448 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0411\u0414 \u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u043d\u0430 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440, \u0430 \u0437\u0430\u0442\u0435\u043c \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u0434\u043e \u043d\u0435\u0433\u043e. \u0418\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a \u044f \u0438 \u043f\u043e\u0441\u0442\u0443\u043f\u043b\u044e. \u041d\u043e \u043f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u044e \u2014 \u044d\u0442\u043e \u043a\u043e\u0441\u0442\u044b\u043b\u044c, \u0430 \u0445\u043e\u0440\u043e\u0448\u0438\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c \u0431\u044b\u043b\u043e \u0431\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0443\u0447\u043a\u0438 <code>grpcAuth.CreateApp()<\/code>, \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0451.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 GitHub \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0441\u0435\u043a\u0440\u0435\u0442\u044b SSH-\u043a\u043b\u044e\u0447 (DEPLOY_SSH_KEY):<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/nh\/xp\/8v\/nhxp8vrun1xoog2q29jlib49fyi.png\" alt=\"\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 SSH-\u043a\u043b\u044e\u0447\u0430 \u0432 \u0441\u0435\u043a\u0440\u0435\u0442\u044b GitHub Actions\" data-src=\"https:\/\/habrastorage.org\/webt\/nh\/xp\/8v\/nhxp8vrun1xoog2q29jlib49fyi.png\"\/><\/p>\n<p>  <\/p>\n<p><em>GitHub, <code>Settings -> Secrets and variables -> Actions<\/code>. \u0418\u043c\u0435\u043d\u0430 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c, \u0442.\u043a. \u043e\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u043d\u0430\u0448\u0435\u043c \u0444\u0430\u0439\u043b\u0435 workflow<\/em><\/p>\n<p>  <\/p>\n<h3 id=\"deploy-v-prodakshen\">\u0414\u0435\u043f\u043b\u043e\u0439 \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d<\/h3>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0432\u0441\u0435 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u0442\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043b\u0438\u0448\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0442\u0435\u0433 \u0438 \u043d\u0430\u0436\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u0434\u0435\u043f\u043b\u043e\u044f. <\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u0433 \u044f \u043e\u0431\u044b\u0447\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">git tag v0.0.1 &amp;&amp; git push origin v0.0.1<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043d\u0430 GitHub \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0441\u0435\u043a\u0446\u0438\u044e Actions \u0438 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 Workflow \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u0432\u043e\u0439 Deploy App:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/_c\/-u\/1t\/_c-u1thsitpb2pg16pyoulwosmo.png\" alt=\"\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043d\u0430 GitHub \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0441\u0435\u043a\u0446\u0438\u044e Actions \u0438 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 Workflow \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u0432\u043e\u0439 Deploy App\" data-src=\"https:\/\/habrastorage.org\/webt\/_c\/-u\/1t\/_c-u1thsitpb2pg16pyoulwosmo.png\"\/><\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c Run workflow \u0438 \u0436\u0434\u0435\u043c. \u0415\u0441\u043b\u0438 \u0432\u0441\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043f\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u043c\u0443 IP: <code>http:\/\/&lt;your_ip>:&lt;http-port>\/url<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043f\u043e\u0440\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043d\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 80, \u0430 \u0442\u043e\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043a\u0430\u0437\u0430\u043d \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430.<\/p>\n<p>  <a name=\"11\"><\/a>  <\/p>\n<h2 id=\"zaklyuchenie\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>  <\/p>\n<p>\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 gRPC \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u2014 \u043e\u0431\u0448\u0438\u0440\u043d\u0430\u044f \u0442\u0435\u043c\u0430, \u0438 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0441\u043b\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u0431\u044a\u044f\u0441\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0440\u0430\u0432\u044f\u0442\u0441\u044f \u0442\u0430\u043a\u0438\u0435 \u0442\u0435\u043c\u044b, \u0438 \u0432 \u0446\u0435\u043b\u043e\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0430 Go, \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0439\u0442\u0435\u0441\u044c \u043d\u0430 \u043c\u043e\u0439 \u043a\u0430\u043d\u0430\u043b. \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0432\u0438\u0434\u0435\u043e\u0432\u0435\u0440\u0441\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u043f\u043e <a href=\"https:\/\/youtu.be\/EURjTg5fw-E\" rel=\"nofollow noopener noreferrer\">\u0441\u0441\u044b\u043b\u043a\u0435<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0423 \u043c\u0435\u043d\u044f \u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0435\u0439, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043d\u0430 Go \u0438 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e. \u041f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0437\u0430 \u043c\u043e\u0435\u0439 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 <a href=\"https:\/\/t.me\/ntuzov\" rel=\"nofollow noopener noreferrer\">\u043c\u043e\u0439 Telegram-\u043a\u0430\u043d\u0430\u043b<\/a>. \u0410\u043d\u043e\u043d\u0441\u044b \u0432\u0441\u0435\u0445 \u0441\u0442\u0430\u0442\u044c\u0435\u0439, \u0440\u043e\u043b\u0438\u043a\u043e\u0432, \u043f\u043e\u0434\u043a\u0430\u0441\u0442\u043e\u0432 \u0438 \u043f\u0440\u043e\u0447\u0435\u0433\u043e \u0431\u0443\u0434\u0443\u0442 \u0442\u0430\u043c. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u044f \u043f\u0438\u0448\u0443 \u0442\u0430\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0435 \u0433\u0430\u0439\u0434\u044b \u0432 \u0431\u043e\u043b\u0435\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/774796\/\"> https:\/\/habr.com\/ru\/articles\/774796\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/pb\/n9\/xn\/pbn9xn0jxtpzgmvfsjhtgqwvt4s.jpeg\" alt=\"\u041f\u0438\u0448\u0435\u043c gRPC \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Go \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438\" data-src=\"https:\/\/habrastorage.org\/webt\/pb\/n9\/xn\/pbn9xn0jxtpzgmvfsjhtgqwvt4s.jpeg\" data-blurred=\"true\"\/><\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043d\u0430\u0443\u0447\u0438\u043c\u0441\u044f \u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 gRPC \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Go \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439, \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u043a \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0443. \u041c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043a\u0430\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c, \u0442\u0430\u043a \u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043c\u044b \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u043c\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u2014 URL Shortener, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0443 \u043c\u0435\u043d\u044f \u0442\u0430\u043a\u0436\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/habr.com\/ru\/companies\/selectel\/articles\/747738\/\">\u0441\u0442\u0430\u0442\u044c\u044f<\/a> \u0438 <a href=\"https:\/\/youtu.be\/rCJvW2xgnk0?si=_JddTOCm1Y-xCgBx\" rel=\"nofollow noopener noreferrer\">\u0432\u0438\u0434\u0435\u043e-\u0433\u0430\u0439\u0434 \u043d\u0430 \u044e\u0442\u0443\u0431\u0435<\/a>. \u041f\u043e\u043f\u0443\u0442\u043d\u043e \u043c\u044b \u043f\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u043c\u0441\u044f \u0441 \u0431\u0430\u0437\u043e\u0432\u044b\u043c\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u0430\u043c\u0438 \u043a \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u0418 \u0432 \u043a\u043e\u043d\u0446\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e GitHub Actions \u043d\u0430 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440.<\/p>\n<p>  <\/p>\n<blockquote><p><a href=\"https:\/\/youtu.be\/EURjTg5fw-E\" rel=\"nofollow noopener noreferrer\">\u0412\u0438\u0434\u0435\u043e-\u0432\u0435\u0440\u0441\u0438\u044f<\/a> \u044d\u0442\u043e\u0433\u043e \u0433\u0430\u0439\u0434\u0430 \u0441 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u044b\u043c\u0438 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438<\/p><\/blockquote>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u0430: <a href=\"https:\/\/github.com\/GolangLessons\/sso\" rel=\"nofollow noopener noreferrer\">https:\/\/github.com\/GolangLessons\/sso<\/a><\/p>\n<p>  <\/p>\n<p>\u0418\u0442\u043e\u0433\u043e, \u043d\u0430\u0448 \u043f\u043b\u0430\u043d:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043d\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 gRPC-\u0441\u0435\u0440\u0432\u0438\u0441<\/li>\n<li>\u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f \u0441 \u0431\u0430\u0437\u043e\u0432\u044b\u043c\u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u2014 \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u044b\u043b\u043e \u0441\u043a\u0443\u0447\u043d\u043e<\/li>\n<li>\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u0435\u043f\u043b\u043e\u0439 \u0432 \u043f\u0440\u043e\u0434 \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0440\u0443\u043a\u0430\u043c\u0438 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043b\u0435\u043d\u044c<\/li>\n<li>\u041f\u043e\u0434\u0440\u0443\u0436\u0438\u043c \u0435\u0433\u043e \u0441 \u0443\u0436\u0435 \u0433\u043e\u0442\u043e\u0432\u044b\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c <a href=\"https:\/\/github.com\/GolangLessons\/url-shortener\" rel=\"nofollow noopener noreferrer\">URL Shortener<\/a> \u2014 \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u043c\u044b\u0441\u043b<\/li>\n<li>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0439 \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043a \u0441\u0432\u043e\u0438\u043c \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430\u043c.<\/p>\n<p>  <\/p>\n<p><strong>\u041a\u0440\u0430\u0442\u043a\u043e \u043e\u0431\u043e \u043c\u043d\u0435<\/strong>: \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 <a href=\"https:\/\/t.me\/ntuzov\" rel=\"nofollow noopener noreferrer\">\u041d\u0438\u043a\u043e\u043b\u0430\u0439 \u0422\u0443\u0437\u043e\u0432<\/a>, \u044f \u043c\u043d\u043e\u0433\u043e \u043b\u0435\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043d\u0430 Go, \u043e\u0447\u0435\u043d\u044c \u043b\u044e\u0431\u043b\u044e \u044d\u0442\u043e\u0442 \u044f\u0437\u044b\u043a. \u0422\u0430\u043a\u0436\u0435 \u0432\u0435\u0434\u0443 \u0441\u0432\u043e\u0439 <a href=\"https:\/\/www.youtube.com\/@nikolay_tuzov\/featured\" rel=\"nofollow noopener noreferrer\">YouTube-\u043a\u0430\u043d\u0430\u043b<\/a>.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-360528","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/360528","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=360528"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/360528\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=360528"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=360528"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=360528"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}