10 Commits
Author SHA1 Message Date
新亮 3e918fce59 #18 add web 2021-03-20 22:05:59 +08:00
新亮 36ef904211 #17 add init.sh 2021-03-13 21:44:56 +08:00
新亮 49ccaf9bec #16 optimize handlergen.sh 2021-03-13 21:23:22 +08:00
新亮 4381a9a6f6 #15 optimize gormgen.sh 2021-03-13 14:14:31 +08:00
新亮 5e5db8917d #14 optimize third_party_request 2021-03-13 13:53:52 +08:00
新亮 f7b3dbae5e #13 Where{FieldName}In Where{FieldName}NotIn 2021-03-06 14:12:13 +08:00
新亮 5d7975056c #12 resubmit middlerware 2021-03-06 13:28:38 +08:00
新亮 499e171d63 #11 code generator 2021-02-28 14:14:56 +08:00
新亮 8d6cd2174e #11 code generator 2021-02-28 10:05:29 +08:00
新亮 3823eee2ca #11 code generator 2021-02-27 09:29:34 +08:00
122 changed files with 9589 additions and 531 deletions
+2 -1
View File
@@ -21,7 +21,8 @@
1. 支持 [gorm](https://gorm.io/gorm) 数据库组件
1. 支持 [go-redis](https://github.com/go-redis/redis/v7) 组件
1. 支持 RESTful API 返回值规范
1. 支持 gormgen、handlergen 代码生成工具
1. 支持 web 界面,使用的 [Light Year Admin 模板](https://gitee.com/yinqi/Light-Year-Admin-Using-Iframe)
## 文档索引
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4303
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because it is too large Load Diff
@@ -0,0 +1,114 @@
.mt-wrapper{
position: relative;
z-index: 1;
}
.mt-nav-bar {
width : 100%;
z-index: 200;
position: absolute;
display: flex;
}
.mt-nav-bar .mt-nav{
background-color: #fff;
}
.mt-nav-panel{
overflow: hidden;
}
.mt-nav-panel ul{
width: 10000px;
}
.mt-nav-panel ul li {
position: relative;
}
.mt-tab-content{
height: 100%;
}
.mt-close-tab {
position: absolute;
font-size: 10px;
width: 16px;
height: 16px;
top: 18px;
right: 10px;
color: #c2c2c2;
cursor: pointer;
display: none;
line-height: 16px;
text-align: center;
vertical-align: 2px;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
-webkit-transform-origin: 100% 50%;
transform-origin: 100% 50%;
}
.mt-close-tab:before {
-webkit-transform: scale(.8);
transform: scale(.8);
display: inline-block;
/*vertical-align: -1px;*/
}
li:hover .mt-close-tab {
display: inline;
}
.mt-hidden-list .mt-close-tab {
display: none !important;
}
.mt-nav-bar a {
cursor: pointer !important;
max-height: 48px;
padding-top: 14px;
padding-bottom: 13px;
}
@media (max-width: 767px){
.mt-tab-content{
/*padding-top: 0 !important;*/
}
}
.mt-tab-content{
-webkit-overflow-scrolling:touch;
overflow:auto;
}
.mt-dragging-tab{
left: auto;
position: absolute !important;
z-index: 9999 !important;
}
.mt-dragging-tab > a{
background: #FBFDFD !important;
}
/*新增*/
.mt-nav-bar .mt-nav .nav-tabs {
margin-bottom: 0px;
border-color: #eceeef;
}
.mt-close-tab:hover {
color: #f96868;
}
.mt-dropdown .caret {
position: absolute;
top: 22px;
}
.mt-dropdown .dropdown-menu {
margin-top: 0px;
}
#contextify-menu {
min-width: 80px!important;
}
.mt-close-tab:hover {
background-color: #f96868;
color: #fff;
}
.mt-nav .nav-tabs a:not([data-type="main"]) {
padding-right: 40px;
}
.mt-nav-tools-left li a,
.mt-nav-tools-right li a {
padding-right: 15px!important;
}
.mt-nav-bar .mt-nav .nav-tabs .mt-dragging {
margin-bottom: -1px;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+134
View File
@@ -0,0 +1,134 @@
;jQuery( function() {
// 停止
$("body").on('click','[data-stopPropagation]',function (e) {
e.stopPropagation();
});
// 滚动条
if($('.lyear-scroll')[0]) {
$('.lyear-scroll').each(function(){
new PerfectScrollbar(this, {
swipeEasing: false,
suppressScrollX: true
});
});
}
// 侧边栏
$(document).on('click', '.lyear-aside-toggler', function() {
$('.lyear-layout-sidebar').toggleClass('lyear-aside-open');
$("body").toggleClass('lyear-layout-sidebar-close');
if ($('.lyear-mask-modal').length == 0) {
$('<div class="lyear-mask-modal"></div>').prependTo('body');
} else {
$( '.lyear-mask-modal' ).remove();
}
});
// 遮罩层
$(document).on('click', '.lyear-mask-modal', function() {
$( this ).remove();
$('.lyear-layout-sidebar').toggleClass('lyear-aside-open');
$('body').toggleClass('lyear-layout-sidebar-close');
});
// 侧边栏导航
$(document).on('click', '.nav-item-has-subnav > a', function() {
$subnavToggle = jQuery( this );
$navHasSubnav = $subnavToggle.parent();
$topHasSubNav = $subnavToggle.parents('.nav-item-has-subnav').last();
$subnav = $navHasSubnav.find('.nav-subnav').first();
$viSubHeight = $navHasSubnav.siblings().find('.nav-subnav:visible').outerHeight();
$scrollBox = $('.lyear-layout-sidebar-info');
$navHasSubnav.siblings().find('.nav-subnav:visible').slideUp(500).parent().removeClass('open');
$subnav.slideToggle( 300, function() {
$navHasSubnav.toggleClass( 'open' );
// 新增滚动条处理
var scrollHeight = 0;
pervTotal = $topHasSubNav.prevAll().length,
boxHeight = $scrollBox.outerHeight(),
innerHeight = $('.sidebar-main').outerHeight(),
thisScroll = $scrollBox.scrollTop(),
thisSubHeight = $(this).outerHeight(),
footHeight = 121;
if (footHeight + innerHeight - boxHeight >= (pervTotal * 48)) {
scrollHeight = pervTotal * 48;
}
if ($subnavToggle.parents('.nav-item-has-subnav').length == 1) {
$scrollBox.animate({scrollTop: scrollHeight}, 300);
} else {
// 子菜单操作
if (typeof($viSubHeight) != 'undefined' && $viSubHeight != null) {
scrollHeight = thisScroll + thisSubHeight - $viSubHeight;
$scrollBox.animate({scrollTop: scrollHeight}, 300);
} else {
if ((thisScroll + boxHeight - $scrollBox[0].scrollHeight) == 0) {
scrollHeight = thisScroll - thisSubHeight;
$scrollBox.animate({scrollTop: scrollHeight}, 300);
}
}
}
});
});
// 读取cookie中的主题设置
var the_logo_bg = $.cookie('the_logo_bg'),
the_header_bg = $.cookie('the_header_bg'),
the_sidebar_bg = $.cookie('the_sidebar_bg');
if (the_logo_bg) $('body').attr('data-logobg', the_logo_bg);
if (the_header_bg) $('body').attr('data-headerbg', the_header_bg);
if (the_sidebar_bg) $('body').attr('data-sidebarbg', the_sidebar_bg);
// 处理主题配色下拉选中
$(".dropdown-skin :radio").each(function(){
var $this = $(this),
radioName = $this.attr('name');
switch (radioName) {
case 'logo_bg':
$this.val() == the_logo_bg && $this.prop("checked", true);
break;
case 'header_bg':
$this.val() == the_header_bg && $this.prop("checked", true);
break;
case 'sidebar_bg':
$this.val() == the_sidebar_bg && $this.prop("checked", true);
}
});
// 设置主题配色
setTheme = function(input_name, data_name) {
$("input[name='"+input_name+"']").click(function(){
$('body').attr(data_name, $(this).val());
$.cookie('the_'+input_name, $(this).val());
});
}
setTheme('sidebar_bg', 'data-sidebarbg');
setTheme('logo_bg', 'data-logobg');
setTheme('header_bg', 'data-headerbg');
// 选项卡
$('#iframe-content').multitabs({
iframe : true,
refresh : 'no', // iframe中页面是否刷新,'no''从不刷新''nav''点击菜单刷新''all''菜单和tab点击都刷新'
nav: {
backgroundColor: '#ffffff',
maxTabs : 35, // 选项卡最大值
},
init : [{
type : 'main',
title : '仪表盘',
url : '/dashboard'
}]
});
$(document).on('click', '.nav-item .multitabs', function() {
$('.nav-item').removeClass('active');
$('.nav-subnav li').removeClass('active');
$(this).parent('li').addClass('active');
$(this).parents('.nav-item-has-subnav').addClass('open').first().addClass('active');
});
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
/*! jquery.cookie v1.4.1 | MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});
File diff suppressed because one or more lines are too long
+141
View File
@@ -0,0 +1,141 @@
;jQuery( function() {
// 工具提示
if($('[data-toggle="tooltip"]')[0]) {
$('[data-toggle="tooltip"]').tooltip({
"container" : 'body',
});
}
// POP弹出框
if($('[data-toggle="popover"]')[0]) {
$('[data-toggle="popover"]').popover();
}
// 关闭卡片
$(document).on('click', '.card-btn-close', function() {
$(this).closest('.card').fadeOut(150, function() {
if ($(this).parent().children().length == 1) {
$(this).parent().remove();
} else {
$(this).remove();
}
});
});
// 卡片收缩与打开
$(document).on('click', '.card-btn-slide', function(){
$(this).toggleClass('rotate-180').closest('.card').find('.card-body').slideToggle();
});
/**
* 如果页面中需要用到滚动条,请先导入perfect-scrollbar.min.js
*/
if($('.lyear-scroll')[0]) {
$('.lyear-scroll').each(function(){
new PerfectScrollbar(this, {
swipeEasing: false,
suppressScrollX: true
});
});
}
// 颜色选取
jQuery('.js-colorpicker').each(function() {
var $colorpicker = jQuery(this);
var $colorpickerMode = $colorpicker.data('colorpicker-mode') ? $colorpicker.data('colorpicker-mode') : 'auto';
$colorpicker.colorpicker({
'format': $colorpickerMode,
});
});
// 日期选择器
jQuery("[data-provide = 'datepicker']").each(function() {
var options = {
language: 'zh-CN', // 默认简体中文
multidateSeparator: ', ' // 默认多个日期用,分隔
}
options = $.extend( options, getDataOptions( $(this) ));
if ( $(this).prop("tagName") != 'INPUT' ) {
options.inputs = [$(this).find('input:first'), $(this).find('input:last')];
}
$(this).datepicker(options);
});
// 时间选择器
jQuery("[data-provide = 'clockpicker']").each(function() {
$(this).clockpicker({
donetext: 'Done'
});
});
// 时间日期选择器
jQuery("[data-provide = 'datetimepicker']").each(function() {
var options = {
locale: moment.locale(),
}
options = $.extend( options, getDataOptions( $(this) ));
if ( $(this).prop("tagName") != 'INPUT' ) {
options.inputs = [$(this).find('input:first'), $(this).find('input:last')];
}
console.log(options);
$(this).datetimepicker(options);
});
// 标签
$('.js-tags-input').each(function() {
var $this = $(this);
$this.tagsInput({
height: $this.data('height') ? $this.data('height') : '36px',
width: '100%',
defaultText: $this.attr("placeholder"),
removeWithBackspace: true,
delimiter: [',']
});
});
// 复选框全选
$("#check-all").change(function () {
if ($boxname = $(this).data('name')) {
$(this).closest('table').find("input[name='" + $boxname + "']").prop('checked', $(this).prop("checked"));
} else {
$(this).closest('table').find(".custom-checkbox input[type='checkbox']").prop('checked', $(this).prop("checked"));
}
});
// iframe打开tab
$(document).on('click', '.js-create-tab', function(){
parent.$(parent.document).data('multitabs').create({
iframe : true,
title : $(this).data('title') ? $(this).data('title') : '标题',
url : $(this).data('url') ? $(this).data('url') : '/dashboard'
}, true);
});
});
// 参考国外模板的写法,获取当前的配置,以data-*(*指插件原有的配置名)
getDataOptions = function(el, castList) {
var options = {};
$.each( $(el).data(), function(key, value){
key = dataToOption(key);
if ( key == 'provide' ) {
return;
}
options[key] = value;
});
return options;
}
dataToOption = function(name) {
return name.replace(/-([a-z])/g, function(x){return x[1].toUpperCase();});
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-12">
<div class="card">
<header class="card-header"><div class="card-title"> 配置信息</div></header>
<div class="card-body">
<h5 class="card-title">MySQL</h5>
<p>主库信息:{{.MySQL.Write.Addr}},账号:{{.MySQL.Write.User}},数据库:{{.MySQL.Write.Name}}</p>
<p>从库信息:{{.MySQL.Read.Addr}},账号:{{.MySQL.Read.User}},数据库:{{.MySQL.Read.Name}}</p>
<p>最大连接数:{{ .MySQL.Base.MaxOpenConn }}</p>
<p>空闲连接数:{{ .MySQL.Base.MaxIdleConn }}</p>
</div>
<div class="card-body">
<h5 class="card-title">Redis</h5>
<p>地址:{{ .Redis.Addr }} </p>
<p>最大连接数:{{ .Redis.PoolSize }} </p>
<p>空闲连接数:{{ .Redis.MinIdleConns }} </p>
</div>
<div class="card-body">
<h5 class="card-title">Mail</h5>
<p>邮箱服务器:{{ .Mail.Host }} </p>
<p>发件人邮箱地址:{{ .Mail.User }} </p>
<p>收件人邮箱地址:{{ .Mail.To }} </p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
+89
View File
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">内存信息</div>
</header>
<div class="card-body">
<p>总量:{{ .MemTotal }}</p>
<p>已使用:{{ .MemUsed }}</p>
<p>使用率:<span class="badge
{{if gt .MemUsedPercent 95.0}} badge-danger
{{else if gt .MemUsedPercent 90.0 }} badge-warning
{{else}} badge-success
{{end}} ">{{ .MemUsedPercent }}%</span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">硬盘信息</div>
</header>
<div class="card-body">
<p>总量:{{ .DiskTotal }}</p>
<p>已使用:{{ .DiskUsed }}</p>
<p>使用率:<span class="badge
{{if gt .DiskUsedPercent 95.0}} badge-danger
{{else if gt .DiskUsedPercent 90.0 }} badge-warning
{{else}} badge-success
{{end}}">{{ .DiskUsedPercent }}%</span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">CPU 信息</div>
</header>
<div class="card-body">
<p>CPU{{ .CpuName }}</p>
<p>核数:{{ .CpuCores }}</p>
<p>CPU 使用率:<span class="badge
{{if gt .CpuUsedPercent 95.0}} badge-danger
{{else if gt .CpuUsedPercent 90.0 }} badge-warning
{{else}} badge-success
{{end}}">{{ .CpuUsedPercent }}%</span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">项目信息</div>
</header>
<div class="card-body">
<p>操作系统:{{ .GoOS }} <span class="badge badge-brown"> {{ .GoArch }} </span></p>
<p>项目地址:{{ .ProjectPath }}</p>
<p>项目域名:{{ .Host }} <span class="badge badge-secondary"> {{ .Env }} </span></p>
<p>GOPATH{{ .GoPath }}</p>
<p>Version{{ .GoVersion }}</p>
<p>Goroutine{{ .Goroutine }}</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title"> <code>gormgen</code> 代码生成工具</div></header>
<div class="card-body">
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code><code>Linux</code> 环境执行。</div>
<div class="form-group">
<label for="tableSelect">选择数据表,可进行多选:</label>
<select multiple="" class="form-control" id="tableSelect" style="height: 260px;">
{{range .}}
<option value="{{ .Name }}">{{ .Name }} -- 备注:{{ .Comment }}</option>
{{end}}
</select>
</div>
<p>
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
执行中...
</button>
</p>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title">执行结果</div></header>
<div class="card-body">
<pre id="resultDiv"></pre>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#btnOk").click(function(){
var tables = [];
var options = $("#tableSelect").find("option:selected");
if (options.length < 1) {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请选择数据表。',
});
return false;
}
$("#resultDiv").text("");
$(this).hide();
$("#btnLoading").show();
for (var i = 0; i < options.length; i++) {
tables.push(options.eq(i).val()); // 将所有的值赋给数组
}
$.post("/gormgen_exec",{tables:tables.join(',')},function (data) {
$("#resultDiv").text(data);
$("#btnLoading").hide();
$("#btnOk").show();
})
})
})
</script>
</body>
</html>
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title"> <code>handlergen</code> 代码生成工具</div></header>
<div class="card-body">
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code><code>Linux</code> 环境执行。</div>
<div class="form-group">
<label for="exampleFormControlInput1">handler 名称,例如:<code>user_handler</code></label>
<input type="text" class="form-control" id="handlerName" placeholder="请输入 handler 名称">
</div>
<p>
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
执行中...
</button>
</p>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title">执行结果</div></header>
<div class="card-body">
<pre id="resultDiv"></pre>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#btnOk").click(function(){
var handlerName = $("#handlerName").val();
if (!handlerName) {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 handler 名称。',
});
return false;
}
$("#resultDiv").text("");
$(this).hide();
$("#btnLoading").show();
$.post("/handlergen_exec",{name:handlerName},function (data) {
$("#resultDiv").text(data);
$("#btnLoading").hide();
$("#btnOk").show();
})
})
})
</script>
</body>
</html>
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title">初始化</div></header>
<div class="card-body">
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code><code>Linux</code> 环境执行。</div>
<p>初始化项目所需信息:</p>
<p><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>user_demo</code> </span></p>
<p>
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
执行中...
</button>
</p>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title">执行结果</div></header>
<div class="card-body">
<pre id="resultDiv"></pre>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#btnOk").click(function(){
$("#resultDiv").text("");
$(this).hide();
$("#btnLoading").show();
$.post("/init_exec","",function (data) {
$("#resultDiv").text(data);
$("#btnLoading").hide();
$("#btnOk").show();
})
})
})
</script>
</body>
</html>
+211
View File
@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
<title>管理面板</title>
<link rel="shortcut icon" type="image/x-icon" href="bootstrap/favicon.ico">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<link rel="stylesheet" type="text/css" href="bootstrap/css/materialdesignicons.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/js/bootstrap-multitabs/multitabs.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/css/animate.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/css/style.min.css">
</head>
<body>
<div class="lyear-layout-web">
<div class="lyear-layout-container">
<!--左侧导航-->
<aside class="lyear-layout-sidebar">
<!-- logo -->
<div id="logo" class="sidebar-header">
<a href="/">
<img src="bootstrap/images/logo-sidebar.png"/>
</a>
</div>
<div class="lyear-layout-sidebar-info lyear-scroll">
<nav class="sidebar-main">
<ul class="nav-drawer">
<li class="nav-item active"> <a class="multitabs" href="/dashboard"><i class="mdi mdi-home"></i> <span>仪表盘</span></a> </li>
<li class="nav-item"> <a class="multitabs" href="/configinfo"><i class="mdi mdi-settings-box"></i> <span>配置信息</span></a> </li>
<li class="nav-item nav-item-has-subnav">
<a href="javascript:void(0)"><i class="mdi mdi-code-not-equal-variant"></i> <span>代码生成</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/init">初始化</a> </li>
<li> <a class="multitabs" href="/gormgen">gormgen</a> </li>
<li> <a class="multitabs" href="/handlergen">handlergen</a> </li>
</ul>
</li>
<li class="nav-item"> <a href="/swagger/index.html" target="_blank" ><i class="mdi mdi-file-document-box"></i> <span>接口文档</span></a> </li>
<li class="nav-item"> <a href="/graphql" target="_blank" ><i class="mdi mdi-file-document-box-search"></i> <span>GraphQL</span></a> </li>
<li class="nav-item"> <a href="/metrics" target="_blank" ><i class="mdi mdi-speedometer"></i> <span>接口指标</span></a> </li>
</ul>
</nav>
</div>
</aside>
<!--End 左侧导航-->
<!--头部信息-->
<header class="lyear-layout-header">
<nav class="navbar">
<div class="navbar-left">
<div class="lyear-aside-toggler">
<span class="lyear-toggler-bar"></span>
<span class="lyear-toggler-bar"></span>
<span class="lyear-toggler-bar"></span>
</div>
</div>
<ul class="navbar-right d-flex align-items-center">
<!--切换主题配色-->
<li class="dropdown dropdown-skin">
<span data-toggle="dropdown" class="icon-item"><i class="mdi mdi-palette"></i></span>
<ul class="dropdown-menu dropdown-menu-right" data-stopPropagation="true">
<li class="drop-title"><p>LOGO</p></li>
<li class="drop-skin-li clearfix">
<span class="inverse">
<input type="radio" name="logo_bg" value="default" id="logo_bg_1" checked>
<label for="logo_bg_1"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_2" id="logo_bg_2">
<label for="logo_bg_2"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_3" id="logo_bg_3">
<label for="logo_bg_3"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_4" id="logo_bg_4">
<label for="logo_bg_4"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_5" id="logo_bg_5">
<label for="logo_bg_5"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_6" id="logo_bg_6">
<label for="logo_bg_6"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_7" id="logo_bg_7">
<label for="logo_bg_7"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_8" id="logo_bg_8">
<label for="logo_bg_8"></label>
</span>
</li>
<li class="drop-title"><p>头部</p></li>
<li class="drop-skin-li clearfix">
<span class="inverse">
<input type="radio" name="header_bg" value="default" id="header_bg_1" checked>
<label for="header_bg_1"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_2" id="header_bg_2">
<label for="header_bg_2"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_3" id="header_bg_3">
<label for="header_bg_3"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_4" id="header_bg_4">
<label for="header_bg_4"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_5" id="header_bg_5">
<label for="header_bg_5"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_6" id="header_bg_6">
<label for="header_bg_6"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_7" id="header_bg_7">
<label for="header_bg_7"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_8" id="header_bg_8">
<label for="header_bg_8"></label>
</span>
</li>
<li class="drop-title"><p>侧边栏</p></li>
<li class="drop-skin-li clearfix">
<span class="inverse">
<input type="radio" name="sidebar_bg" value="default" id="sidebar_bg_1" checked>
<label for="sidebar_bg_1"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_2" id="sidebar_bg_2">
<label for="sidebar_bg_2"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_3" id="sidebar_bg_3">
<label for="sidebar_bg_3"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_4" id="sidebar_bg_4">
<label for="sidebar_bg_4"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_5" id="sidebar_bg_5">
<label for="sidebar_bg_5"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_6" id="sidebar_bg_6">
<label for="sidebar_bg_6"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_7" id="sidebar_bg_7">
<label for="sidebar_bg_7"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_8" id="sidebar_bg_8">
<label for="sidebar_bg_8"></label>
</span>
</li>
</ul>
</li>
<!--切换主题配色-->
</ul>
</nav>
</header>
<!--End 头部信息-->
<!--页面主要内容-->
<main class="lyear-layout-content">
<div id="iframe-content"></div>
</main>
<!--End 页面主要内容-->
</div>
</div>
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="bootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bootstrap/js/perfect-scrollbar.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
<script type="text/javascript" src="bootstrap/js/jquery.cookie.min.js"></script>
<script type="text/javascript" src="bootstrap/js/index.min.js"></script>
</body>
</html>
+15
View File
@@ -0,0 +1,15 @@
## 执行命令
在根目录下执行脚本:`./scripts/gormgen.sh addr user pass name tables`
- addr:数据库地址,例如:127.0.0.1:3306
- user:账号,例如:root
- pass:密码,例如:root
- name:数据库名称,例如:go_gin_api
- tables:表名,默认为 *,多个表名可用“,”分割,例如:user_demo
例如:
```
./scripts/gormgen.sh 127.0.0.1:3306 root root go_gin_api user_demo
```
## 参考
- https://github.com/MohamedBassem/gormgen
+37
View File
@@ -0,0 +1,37 @@
package main
import (
"flag"
"log"
"os"
"strings"
"github.com/xinliangnote/go-gin-api/cmd/gormgen/pkg"
)
var (
input string
structs []string
)
func init() {
flagStructs := flag.String("structs", "", "[Required] The name of schema structs to generate structs for, comma seperated\n")
flagInput := flag.String("input", "", "[Required] The name of the input file dir\n")
flag.Parse()
if *flagStructs == "" || *flagInput == "" {
flag.Usage()
os.Exit(1)
}
structs = strings.Split(*flagStructs, ",")
input = *flagInput
}
func main() {
gen := pkg.NewGenerator(input)
p := pkg.NewParser(input)
if err := gen.ParserAST(p, structs).Generate().Format().Flush(); err != nil {
log.Fatalln(err)
}
}
+126
View File
@@ -0,0 +1,126 @@
package pkg
import (
"bytes"
"errors"
"fmt"
"go/format"
"io/ioutil"
"log"
"strings"
"github.com/jinzhu/gorm"
)
// fieldConfig
type fieldConfig struct {
FieldName string
ColumnName string
FieldType string
HumpName string
}
// structConfig
type structConfig struct {
config
StructName string
OnlyFields []fieldConfig
OptionFields []fieldConfig
}
type ImportPkg struct {
Pkg string
}
type structHelpers struct {
Titelize func(string) string
}
type config struct {
PkgName string
Helpers structHelpers
QueryBuilderName string
}
// The Generator is the one responsible for generating the code, adding the imports, formating, and writing it to the file.
type Generator struct {
buf map[string]*bytes.Buffer
inputFile string
config config
structConfigs []structConfig
}
// NewGenerator function creates an instance of the generator given the name of the output file as an argument.
func NewGenerator(outputFile string) *Generator {
return &Generator{
buf: map[string]*bytes.Buffer{},
inputFile: outputFile,
}
}
// ParserAST parse by go file
func (g *Generator) ParserAST(p *Parser, structs []string) (ret *Generator) {
for _, v := range structs {
g.buf[gorm.ToDBName(v)] = new(bytes.Buffer)
}
g.structConfigs = p.Parse()
g.config.PkgName = p.pkg.Name
g.config.Helpers = structHelpers{
Titelize: strings.Title,
}
g.config.QueryBuilderName = SQLColumnToHumpStyle(p.pkg.Name) + "QueryBuilder"
return g
}
func (g *Generator) checkConfig() (err error) {
if len(g.config.PkgName) == 0 {
err = errors.New("package name dose'n set")
return
}
for i := 0; i < len(g.structConfigs); i++ {
g.structConfigs[i].config = g.config
}
return
}
// Generate executes the template and store it in an internal buffer.
func (g *Generator) Generate() *Generator {
if err := g.checkConfig(); err != nil {
panic(err)
}
for _, v := range g.structConfigs {
if _, ok := g.buf[gorm.ToDBName(v.StructName)]; !ok {
continue
}
if err := outputTemplate.Execute(g.buf[gorm.ToDBName(v.StructName)], v); err != nil {
panic(err)
}
}
return g
}
// Format function formats the output of the generation.
func (g *Generator) Format() *Generator {
for k := range g.buf {
formattedOutput, err := format.Source(g.buf[k].Bytes())
if err != nil {
panic(err)
}
g.buf[k] = bytes.NewBuffer(formattedOutput)
}
return g
}
// Flush function writes the output to the output file.
func (g *Generator) Flush() error {
for k := range g.buf {
filename := g.inputFile + "/gen_" + strings.ToLower(k) + ".go"
if err := ioutil.WriteFile(filename, g.buf[k].Bytes(), 0777); err != nil {
log.Fatalln(err)
}
fmt.Println(" └── file : ", fmt.Sprintf("%s_repo/gen_%s.go", strings.ToLower(k), strings.ToLower(k)))
}
return nil
}
+118
View File
@@ -0,0 +1,118 @@
package pkg
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"log"
"strings"
"github.com/jinzhu/gorm"
)
// The Parser is used to parse a directory and expose information about the structs defined in the files of this directory.
type Parser struct {
dir string
pkg *build.Package
parsedFiles []*ast.File
}
// NewParser create a new parser instance.
func NewParser(dir string) *Parser {
return &Parser{
dir: dir,
}
}
// getPackage parse dir get go file and package
func (p *Parser) getPackage() {
pkg, err := build.Default.ImportDir(p.dir, build.ImportComment)
if err != nil {
log.Fatalf("cannot process directory %s: %s", p.dir, err)
}
p.pkg = pkg
}
// parseGoFiles parse go file
func (p *Parser) parseGoFiles() {
var parsedFiles []*ast.File
fs := token.NewFileSet()
for _, file := range p.pkg.GoFiles {
file = p.dir + "/" + file
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", file, err)
}
parsedFiles = append(parsedFiles, parsedFile)
}
p.parsedFiles = parsedFiles
}
// parseTypes parse type of struct
func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
ast.Inspect(file, func(n ast.Node) bool {
decl, ok := n.(*ast.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
var (
data structConfig
)
typeSpec, _ok := spec.(*ast.TypeSpec)
if !_ok {
continue
}
// We only care about struct declaration (for now)
var structType *ast.StructType
if structType, ok = typeSpec.Type.(*ast.StructType); !ok {
continue
}
data.StructName = typeSpec.Name.Name
for _, v := range structType.Fields.List {
var (
optionField fieldConfig
)
if t, _ok := v.Type.(*ast.Ident); _ok {
optionField.FieldType = t.String()
} else {
if v.Tag != nil {
if strings.Contains(v.Tag.Value, "gorm") && strings.Contains(v.Tag.Value, "time") {
optionField.FieldType = "time.Time"
}
}
}
if len(v.Names) > 0 {
optionField.FieldName = v.Names[0].String()
optionField.ColumnName = gorm.ToDBName(optionField.FieldName)
optionField.HumpName = SQLColumnToHumpStyle(optionField.ColumnName)
}
data.OptionFields = append(data.OptionFields, optionField)
}
ret = append(ret, data)
}
return true
})
return
}
// Parse should be called before any type querying for the parser. It takes the directory to be parsed and extracts all the structs defined in this directory.
func (p *Parser) Parse() (ret []structConfig) {
var (
data []structConfig
)
p.getPackage()
p.parseGoFiles()
for _, f := range p.parsedFiles {
data = append(data, p.parseTypes(f)...)
}
return data
}
+171
View File
@@ -0,0 +1,171 @@
package pkg
import "text/template"
// Make sure that the template compiles during package initialization
func parseTemplateOrPanic(t string) *template.Template {
tpl, err := template.New("output_template").Parse(t)
if err != nil {
panic(err)
}
return tpl
}
var outputTemplate = parseTemplateOrPanic(`
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package {{.PkgName}}
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *{{.StructName}} {
return new({{.StructName}})
}
func NewQueryBuilder() *{{.QueryBuilderName}} {
return new({{.QueryBuilderName}})
}
func (t *{{.StructName}}) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *{{.StructName}}) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *{{.StructName}}) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&{{.StructName}}{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type {{.QueryBuilderName}} struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *{{.QueryBuilderName}}) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *{{.QueryBuilderName}}) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&{{.StructName}}{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *{{.QueryBuilderName}}) First(db *gorm.DB) (*{{.StructName}}, error) {
ret := &{{.StructName}}{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *{{.QueryBuilderName}}) QueryOne(db *gorm.DB) (*{{.StructName}}, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *{{.QueryBuilderName}}) QueryAll(db *gorm.DB) ([]*{{.StructName}}, error) {
var ret []*{{.StructName}}
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *{{.QueryBuilderName}}) Limit(limit int) *{{.QueryBuilderName}} {
qb.limit = limit
return qb
}
func (qb *{{.QueryBuilderName}}) Offset(offset int) *{{.QueryBuilderName}} {
qb.offset = offset
return qb
}
{{$queryBuilderName := .QueryBuilderName}}
{{range .OptionFields}}
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}(p db_repo.Predicate, value {{.FieldType}}) *{{$queryBuilderName}} {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", p),
value,
})
return qb
}
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}In(value []{{.FieldType}}) *{{$queryBuilderName}} {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", "IN"),
value,
})
return qb
}
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}NotIn(value []{{.FieldType}}) *{{$queryBuilderName}} {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", "NOT IN"),
value,
})
return qb
}
func (qb *{{$queryBuilderName}}) OrderBy{{call $.Helpers.Titelize .FieldName}}(asc bool) *{{$queryBuilderName}} {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "{{.ColumnName}} " + order)
return qb
}
{{end}}
`)
+18
View File
@@ -0,0 +1,18 @@
package pkg
import "strings"
// SQLColumnToHumpStyle sql转换成驼峰模式
func SQLColumnToHumpStyle(in string) (ret string) {
for i := 0; i < len(in); i++ {
if i > 0 && in[i-1] == '_' && in[i] != '_' {
s := strings.ToUpper(string(in[i]))
ret += s
} else if in[i] == '_' {
continue
} else {
ret += string(in[i])
}
}
return
}
+102
View File
@@ -0,0 +1,102 @@
## 执行命令
```$xslt
// test_handler 为 ./internal/api/controller/ 中的包名
./scripts/handlergen.sh test_handler
```
## 模板文件参考
```go
package test_handler
import (
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// Create 创建用户
// @Tags Test
// @Router /test/create [post]
Create() core.HandlerFunc
// Update 编辑用户
// @Tags Test
// @Router /test/update [post]
Update() core.HandlerFunc
// Delete 删除用户
// @Tags Test
// @Router /test/delete [post]
Delete() core.HandlerFunc
// Detail 用户详情
// @Tags Test
// @Router /test/detail [post]
Detail() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
func (h *handler) i() {}
```
以上会生成 4 个文件
- func_create.go
- func_update.go
- func_delete.go
- func_detail.go
## func_create.go 参考
```go
package test_handler
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type createRequest struct{}
type createResponse struct{}
// Create 创建用户
// @Summary 创建用户
// @Description 创建用户
// @Tags Test
// @Accept json
// @Produce json
// @Param Request body createRequest true "请求信息"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /test/create [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
}
}
```
+108
View File
@@ -0,0 +1,108 @@
package main
import (
"flag"
"fmt"
"go/token"
"io/ioutil"
"log"
"os"
"strings"
"unicode"
"github.com/dave/dst"
"github.com/dave/dst/decorator"
)
var handlerName string
func init() {
handler := flag.String("handler", "", "请输入需要生成的 handler 名称\n")
flag.Parse()
handlerName = strings.ToLower(*handler)
}
func main() {
fs := token.NewFileSet()
filePath := fmt.Sprintf("./internal/api/controller/%s", handlerName)
parsedFile, err := decorator.ParseFile(fs, filePath+"/handler.go", nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", filePath, err)
}
files, _ := ioutil.ReadDir(filePath)
if len(files) > 1 {
log.Fatalf("请先确保 %s 目录中,有且仅有 handler.go 一个文件。", filePath)
}
dst.Inspect(parsedFile, func(n dst.Node) bool {
decl, ok := n.(*dst.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
typeSpec, _ok := spec.(*dst.TypeSpec)
if !_ok {
continue
}
var interfaceType *dst.InterfaceType
if interfaceType, ok = typeSpec.Type.(*dst.InterfaceType); !ok {
continue
}
for _, v := range interfaceType.Methods.List {
if len(v.Names) > 0 {
if v.Names[0].String() == "i" {
continue
}
filepath := "./internal/api/controller/" + handlerName
filename := fmt.Sprintf("%s/func_%s.go", filepath, strings.ToLower(v.Names[0].String()))
funcFile, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open func file error %v\n", err.Error())
}
fmt.Println(" └── file : ", filename)
funcContent := fmt.Sprintf("package %s\n\n", handlerName)
funcContent += "import (\n"
funcContent += `"github.com/xinliangnote/go-gin-api/internal/pkg/core"`
funcContent += "\n)\n\n"
funcContent += fmt.Sprintf("\n\ntype %sRequest struct {}\n\n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("type %sResponse struct {}\n\n", Lcfirst(v.Names[0].String()))
// 首行注释
funcContent += fmt.Sprintf("%s\n", v.Decorations().Start.All()[0])
nameArr := strings.Split(v.Decorations().Start.All()[0], v.Names[0].String())
funcContent += fmt.Sprintf("// @Summary%s \n", nameArr[1])
funcContent += fmt.Sprintf("// @Description%s \n", nameArr[1])
// Tags
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[1])
funcContent += fmt.Sprintf("// @Accept json \n")
funcContent += fmt.Sprintf("// @Produce json \n")
funcContent += fmt.Sprintf("// @Param Request body %sRequest true \"请求信息\" \n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("// @Success 200 {object} %sResponse \n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("// @Failure 400 {object} code.Failure \n")
// Router
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[2])
funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(c core.Context) {\n\n}}", v.Names[0].String())
funcFile.WriteString(funcContent)
funcFile.Close()
}
}
}
return true
})
}
func Lcfirst(str string) string {
for i, v := range str {
return string(unicode.ToLower(v)) + str[i+1:]
}
return ""
}
+11
View File
@@ -0,0 +1,11 @@
## 执行命令
在根目录下执行脚本:`./scripts/init.sh addr user pass name`
- addr:数据库地址,例如:127.0.0.1:3306
- user:账号,例如:root
- pass:密码,例如:root
- name:数据库名称,例如:go_gin_api
例如:
```
./scripts/init.sh 127.0.0.1:3306 root root go_gin_api
```
+52
View File
@@ -0,0 +1,52 @@
package main
import (
"flag"
"log"
"strings"
"github.com/xinliangnote/go-gin-api/cmd/init/db/mysql"
)
var (
dbAddr string
dbUser string
dbPass string
dbName string
)
func init() {
addr := flag.String("addr", "", "请输入 db 地址,例如:127.0.0.1:3306\n")
user := flag.String("user", "", "请输入 db 用户名\n")
pass := flag.String("pass", "", "请输入 db 密码\n")
name := flag.String("name", "", "请输入 db 名称\n")
flag.Parse()
dbAddr = *addr
dbUser = *user
dbPass = *pass
dbName = strings.ToLower(*name)
}
func main() {
// 初始化 DB
db, err := mysql.New(dbAddr, dbUser, dbPass, dbName)
if err != nil {
log.Fatal("new db err: ", err.Error())
}
defer func() {
if err := db.DbClose(); err != nil {
log.Fatal("db close err: ", err.Error())
}
}()
// 创建 user_demo 表
err = db.GetDb().Exec(mysql.CreateUserDemoTableSql()).Error
if err != nil {
log.Fatal("create user_demo table err: ", err.Error())
}
log.Println("create user_demo table success")
}
+63
View File
@@ -0,0 +1,63 @@
package mysql
import (
"fmt"
"github.com/pkg/errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
var _ Repo = (*dbRepo)(nil)
type Repo interface {
i()
GetDb() *gorm.DB
DbClose() error
}
type dbRepo struct {
DbConn *gorm.DB
}
func New(dbAddr, dbUser, dbPass, dbName string) (Repo, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
dbUser,
dbPass,
dbAddr,
dbName,
true,
"Local")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
})
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName))
}
db.Set("gorm:table_options", "CHARSET=utf8mb4")
return &dbRepo{
DbConn: db,
}, nil
}
func (d *dbRepo) i() {}
func (d *dbRepo) GetDb() *gorm.DB {
return d.DbConn
}
func (d *dbRepo) DbClose() error {
sqlDB, err := d.DbConn.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
+27
View File
@@ -0,0 +1,27 @@
package mysql
//CREATE TABLE `user_demo` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
//`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
//`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',
//`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
//PRIMARY KEY (`id`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';
func CreateUserDemoTableSql() (sql string) {
sql = "CREATE TABLE `user_demo` ("
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
sql += "`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',"
sql += "`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',"
sql += "`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',"
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',"
sql += "PRIMARY KEY (`id`)"
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';"
return
}
+297
View File
@@ -0,0 +1,297 @@
package main
import (
"database/sql"
"flag"
"fmt"
"log"
"os"
"regexp"
"strings"
"github.com/xinliangnote/go-gin-api/cmd/mysqlmd/mysql"
"gorm.io/gorm"
)
type tableInfo struct {
Name string `db:"table_name"` // name
Comment sql.NullString `db:"table_comment"` // comment
}
type tableColumn struct {
OrdinalPosition uint16 `db:"ORDINAL_POSITION"` // position
ColumnName string `db:"COLUMN_NAME"` // name
ColumnType string `db:"COLUMN_TYPE"` // column_type
DataType string `db:"DATA_TYPE"` // data_type
ColumnKey sql.NullString `db:"COLUMN_KEY"` // key
IsNullable string `db:"IS_NULLABLE"` // nullable
Extra sql.NullString `db:"EXTRA"` // extra
ColumnComment sql.NullString `db:"COLUMN_COMMENT"` // comment
ColumnDefault sql.NullString `db:"COLUMN_DEFAULT"` // default value
}
var (
dbAddr string
dbUser string
dbPass string
dbName string
genTables string
)
func init() {
addr := flag.String("addr", "", "请输入 db 地址,例如:127.0.0.1:3306\n")
user := flag.String("user", "", "请输入 db 用户名\n")
pass := flag.String("pass", "", "请输入 db 密码\n")
name := flag.String("name", "", "请输入 db 名称\n")
table := flag.String("tables", "*", "请输入 table 名称,默认为“*”,多个可用“,”分割\n")
flag.Parse()
dbAddr = *addr
dbUser = *user
dbPass = *pass
dbName = strings.ToLower(*name)
genTables = strings.ToLower(*table)
}
func main() {
// 初始化 DB
db, err := mysql.New(dbAddr, dbUser, dbPass, dbName)
if err != nil {
log.Fatal("new db err", err)
}
defer func() {
if err := db.DbClose(); err != nil {
log.Println("db close err", err)
}
}()
tables, err := queryTables(db.GetDb(), dbName, genTables)
if err != nil {
log.Println("query tables of database err", err)
return
}
for _, table := range tables {
filepath := "./internal/api/repository/db_repo/" + table.Name + "_repo"
_ = os.Mkdir(filepath, 0766)
fmt.Println("create dir : ", filepath)
mdName := fmt.Sprintf("%s/gen_table.md", filepath)
mdFile, err := os.OpenFile(mdName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("markdown file error %v\n", err.Error())
return
}
fmt.Println(" └── file : ", table.Name+"_repo/gen_table.md")
modelName := fmt.Sprintf("%s/gen_model.go", filepath)
modelFile, err := os.OpenFile(modelName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open model file error %v\n", err.Error())
return
}
fmt.Println(" └── file : ", table.Name+"_repo/gen_model.go")
modelContent := fmt.Sprintf("package %s%s\n", table.Name, "_repo")
modelContent += fmt.Sprintf(`import "time"`)
modelContent += fmt.Sprintf("\n\n// %s \n", table.Comment.String)
modelContent += fmt.Sprintf("//go:generate gormgen -structs %s -input . \n", capitalize(table.Name))
modelContent += fmt.Sprintf("type %s struct {\n", capitalize(table.Name))
tableContent := fmt.Sprintf("#### %s.%s \n", dbName, table.Name)
if table.Comment.String != "" {
tableContent += table.Comment.String + "\n"
}
tableContent += "\n" +
"| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |\n" +
"| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n"
columnInfo, columnInfoErr := queryTableColumn(db.GetDb(), dbName, table.Name)
if columnInfoErr != nil {
continue
}
for _, info := range columnInfo {
tableContent += fmt.Sprintf(
"| %d | %s | %s | %s | %s | %s | %s | %s |\n",
info.OrdinalPosition,
info.ColumnName,
strings.ReplaceAll(strings.ReplaceAll(info.ColumnComment.String, "|", "\\|"), "\n", ""),
info.ColumnType,
info.ColumnKey.String,
info.IsNullable,
info.Extra.String,
info.ColumnDefault.String,
)
if textType(info.DataType) == "time.Time" {
modelContent += fmt.Sprintf("%s %s `%s` // %s\n", capitalize(info.ColumnName), textType(info.DataType), "gorm:\"time\"", info.ColumnComment.String)
} else {
modelContent += fmt.Sprintf("%s %s // %s\n", capitalize(info.ColumnName), textType(info.DataType), info.ColumnComment.String)
}
}
mdFile.WriteString(tableContent)
mdFile.Close()
modelContent += "}\n"
modelFile.WriteString(modelContent)
modelFile.Close()
}
}
func queryTables(db *gorm.DB, dbName string, tableName string) ([]tableInfo, error) {
var tableCollect []tableInfo
var tableArray []string
var commentArray []sql.NullString
sqlTables := fmt.Sprintf("SELECT `table_name`,`table_comment` FROM `information_schema`.`tables` WHERE `table_schema`= '%s'", dbName)
rows, err := db.Raw(sqlTables).Rows()
if err != nil {
return tableCollect, err
}
defer rows.Close()
for rows.Next() {
var info tableInfo
err = rows.Scan(&info.Name, &info.Comment)
if err != nil {
fmt.Printf("execute query tables action error,had ignored, detail is [%v]\n", err.Error())
continue
}
tableCollect = append(tableCollect, info)
tableArray = append(tableArray, info.Name)
commentArray = append(commentArray, info.Comment)
}
// filter tables when specified tables params
if tableName != "*" {
tableCollect = nil
chooseTables := strings.Split(tableName, ",")
indexMap := make(map[int]int)
for _, item := range chooseTables {
subIndexMap := getTargetIndexMap(tableArray, item)
for k, v := range subIndexMap {
if _, ok := indexMap[k]; ok {
continue
}
indexMap[k] = v
}
}
if len(indexMap) != 0 {
for _, v := range indexMap {
var info tableInfo
info.Name = tableArray[v]
info.Comment = commentArray[v]
tableCollect = append(tableCollect, info)
}
}
}
return tableCollect, err
}
func queryTableColumn(db *gorm.DB, dbName string, tableName string) ([]tableColumn, error) {
// 定义承载列信息的切片
var columns []tableColumn
sqlTableColumn := fmt.Sprintf("SELECT `ORDINAL_POSITION`,`COLUMN_NAME`,`COLUMN_TYPE`,`DATA_TYPE`,`COLUMN_KEY`,`IS_NULLABLE`,`EXTRA`,`COLUMN_COMMENT`,`COLUMN_DEFAULT` FROM `information_schema`.`columns` WHERE `table_schema`= '%s' AND `table_name`= '%s' ORDER BY `ORDINAL_POSITION` ASC",
dbName, tableName)
rows, err := db.Raw(sqlTableColumn).Rows()
if err != nil {
fmt.Printf("execute query table column action error, detail is [%v]\n", err.Error())
return columns, err
}
defer rows.Close()
for rows.Next() {
var column tableColumn
err = rows.Scan(
&column.OrdinalPosition,
&column.ColumnName,
&column.ColumnType,
&column.DataType,
&column.ColumnKey,
&column.IsNullable,
&column.Extra,
&column.ColumnComment,
&column.ColumnDefault)
if err != nil {
fmt.Printf("query table column scan error, detail is [%v]\n", err.Error())
return columns, err
}
columns = append(columns, column)
}
return columns, err
}
func getTargetIndexMap(tableNameArr []string, item string) map[int]int {
indexMap := make(map[int]int)
for i := 0; i < len(tableNameArr); i++ {
if match, _ := regexp.MatchString(item, tableNameArr[i]); match {
if _, ok := indexMap[i]; ok {
continue
}
indexMap[i] = i
}
}
return indexMap
}
func capitalize(s string) string {
var upperStr string
chars := strings.Split(s, "_")
for _, val := range chars {
vv := []rune(val)
for i := 0; i < len(vv); i++ {
if i == 0 {
if vv[i] >= 97 && vv[i] <= 122 {
vv[i] -= 32
upperStr += string(vv[i])
}
} else {
upperStr += string(vv[i])
}
}
}
return upperStr
}
func textType(s string) string {
var mysqlTypeToGoType = map[string]string{
"tinyint": "int32",
"smallint": "int32",
"mediumint": "int32",
"int": "int32",
"integer": "int64",
"bigint": "int64",
"float": "float64",
"double": "float64",
"decimal": "float64",
"date": "string",
"time": "string",
"year": "string",
"datetime": "time.Time",
"timestamp": "time.Time",
"char": "string",
"varchar": "string",
"tinyblob": "string",
"tinytext": "string",
"blob": "string",
"text": "string",
"mediumblob": "string",
"mediumtext": "string",
"longblob": "string",
"longtext": "string",
}
return mysqlTypeToGoType[s]
}
+63
View File
@@ -0,0 +1,63 @@
package mysql
import (
"fmt"
"github.com/pkg/errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
var _ Repo = (*dbRepo)(nil)
type Repo interface {
i()
GetDb() *gorm.DB
DbClose() error
}
type dbRepo struct {
DbConn *gorm.DB
}
func New(dbAddr, dbUser, dbPass, dbName string) (Repo, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
dbUser,
dbPass,
dbAddr,
dbName,
true,
"Local")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
})
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName))
}
db.Set("gorm:table_options", "CHARSET=utf8mb4")
return &dbRepo{
DbConn: db,
}, nil
}
func (d *dbRepo) i() {}
func (d *dbRepo) GetDb() *gorm.DB {
return d.DbConn
}
func (d *dbRepo) DbClose() error {
sqlDB, err := d.DbConn.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
+4 -9
View File
@@ -54,15 +54,10 @@ type Config struct {
ExpireDuration time.Duration `toml:"expireDuration"`
} `toml:"jwt"`
Aes struct {
Key string `toml:"key"`
Iv string `toml:"iv"`
} `toml:"aes"`
Rsa struct {
Private string `toml:"private"`
Public string `toml:"public"`
} `toml:"rsa"`
URLToken struct {
Secret string `toml:"secret"`
ExpireDuration time.Duration `toml:"expireDuration"`
} `toml:"urlToken"`
}
func init() {
+3 -43
View File
@@ -34,46 +34,6 @@ to = "" # 收件人邮箱,多个可以逗号(,)分割
secret = 'i1ydX9RtHyuJTrw7frcu' # JWT secret
expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时)
[aes]
key = 'IgkibX71IEf382PT'
iv = 'IgkibX71IEf382PT'
[rsa]
private = '-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA1O3p0JN0/RrP7eY3f81izPf16FS0WMNGCJkd+y5c6yBzUvN0
IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNom/UCAzAYEgdIsNee5LSN/7e0T2/Q
vsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT34GGRdrSJ+m4iVk/Kt95tavBNk+ND
VSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e9Tn8f99M2VMqpzXHXZzv1ABmqufz
S20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HMxLYEFN9h2MWYgxLm9Z0rLMrWwMM+
E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbzowIDAQABAoIBAQCO1RE1ItUlO6kj
Un0ENAgEqojAUqGvsT33Yo7kAZO+/cOeb0UEqk0iq5bf7L9ncBynWDg6ZPc6X3/g
wdFdKxAvHck9zjM3VL+EMP+bNyrR0K8ZYk5Kx+Q/PEK+Mp8dfRdgggAUsZaNWB+a
rVVspiMo1wo28KBl5x8NevTnJkOLqXAyB7UyLWqnOL1fb988lZvZPR7ZUYroVIZa
pyXtZcafIJeKyQ3bvWI5+eFqOe61Z4Bx1+TpfZ3fKfSDW0vhxzNqaimOa8jSXtMJ
jMeOctL4nZ0TPo/jS3I+XlaH4ZQlFLuUWGscpxwfEeBN23I8HRLkZXJsw66yvRN3
s4bUKPXRAoGBAP/3oSZAECvfsYYzs76tnrAmR/0GxCqgguxDlWn5DowQzdWFOdHC
ZbTo/hUVoMSQnO1EKCFlnBS+wg/3TuIzUO0ewC1aeT7qHbOMDl0zKbNpS2Z9/j+U
zro+qz7XmkWolMCfmDrCrw9CtCxcMSII+ajbI8SAgFVMz9XnDt+xW9E9AoGBANT0
4F6kCUJTEyqf2+v84tjQ2wGIF6XtZPU9JR806zeMyahQ9F6z3hY8BYb0tIy5b3uJ
VlJ9TG1qg/t59TWxIq43mYSUJHe0aJi3ilooObQtHlhPu8nwmmX47sX0PyG2hMoD
kBVxTpTDmBaDz7O9uBnlMXJN5qEygctaixpEbmZfAoGBAMBA9kEMjRjnAyeRXcgy
D6aumhNqKZz6wltCx864yjxZwsBFOJBcOpgPCAg+HmqFU9jCAIJVF05dmNT1I8Ky
WG5BUoa+FaMzpOtenstRylh/Far9pyGKW1t4BpdEyRLY9CFZvbUk1OfZagqHlD/E
DgDN16eX/MwUzWYUDg/l3tjhAoGBAKGip/ZNjVWRFpggs9z/mfK1O7WC5Wgksp9N
ZLK2CN6l9p3RrFmBLk00C4HulGfHi+15RVLhFbRqx3iFje/N3iPbwaMWikNtZIKd
tN5Pb9To9gJTqpZRD+/cLOeFRrHBBjMK1z7fPKS/fN2B+JFVq7nD827t3+J0In4F
4FT0odMDAoGBAJk3ELB/FHY8xzZ4jF1wG/a1CK681Xm6SuU5KIELDSAUNoou6OPG
mS8gU20MMPAeV2z7khyDcSxlHsUyL73eLeaakbQov9NMW7cc99XX4wnP4W7FRpmr
QbHmKuHIRFHCFv+XX8c0aK2mDZMUlzJdy4FgD/YCEZ7kZMZKyvZW/ZuV
-----END RSA PRIVATE KEY-----'
public = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1O3p0JN0/RrP7eY3f81i
zPf16FS0WMNGCJkd+y5c6yBzUvN0IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNo
m/UCAzAYEgdIsNee5LSN/7e0T2/QvsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT3
4GGRdrSJ+m4iVk/Kt95tavBNk+NDVSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e
9Tn8f99M2VMqpzXHXZzv1ABmqufzS20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HM
xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz
owIDAQAB
-----END PUBLIC KEY-----'
[urlToken]
secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)
+2 -2
View File
@@ -169,7 +169,7 @@ var doc = `{
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户 - 更新 is_deleted = 1",
"description": "删除用户",
"consumes": [
"application/json"
],
@@ -179,7 +179,7 @@ var doc = `{
"tags": [
"User"
],
"summary": "删除用户 - 更新 is_deleted = 1",
"summary": "删除用户",
"parameters": [
{
"type": "integer",
+2 -2
View File
@@ -152,7 +152,7 @@
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户 - 更新 is_deleted = 1",
"description": "删除用户",
"consumes": [
"application/json"
],
@@ -162,7 +162,7 @@
"tags": [
"User"
],
"summary": "删除用户 - 更新 is_deleted = 1",
"summary": "删除用户",
"parameters": [
{
"type": "integer",
+2 -2
View File
@@ -176,7 +176,7 @@ paths:
patch:
consumes:
- application/json
description: 删除用户 - 更新 is_deleted = 1
description: 删除用户
parameters:
- description: 用户ID
in: path
@@ -203,7 +203,7 @@ paths:
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 删除用户 - 更新 is_deleted = 1
summary: 删除用户
tags:
- User
/user/info/{username}:
+8 -2
View File
@@ -4,31 +4,37 @@ go 1.15
require (
github.com/99designs/gqlgen v0.13.0
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/dave/dst v0.26.2
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/pprof v1.2.1
github.com/gin-gonic/gin v1.6.3
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/go-openapi/spec v0.20.0 // indirect
github.com/go-redis/redis/v7 v7.4.0
github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.5.4 // indirect
github.com/jinzhu/gorm v1.9.16
github.com/koketama/errors v1.0.0
github.com/onsi/ginkgo v1.14.2 // indirect
github.com/onsi/gomega v1.10.4 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v0.9.3
github.com/rs/cors v1.7.0
github.com/shirou/gopsutil v3.21.2+incompatible
github.com/spf13/cast v1.3.0
github.com/spf13/viper v1.7.1
github.com/swaggo/gin-swagger v1.3.0
github.com/swaggo/swag v1.7.0
github.com/tklauser/go-sysconf v0.3.4 // indirect
github.com/vektah/gqlparser/v2 v2.1.0
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.16.0
golang.org/x/mod v0.4.0 // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a // indirect
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a
google.golang.org/grpc v1.27.0
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+53 -2
View File
@@ -19,11 +19,14 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
@@ -33,6 +36,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -55,9 +59,17 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/dst v0.26.2 h1:lnxLAKI3tx7MgLNVDirFCsDTlTG9nKTk7GcptKcWSwY=
github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU=
github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8=
github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
@@ -65,6 +77,8 @@ github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMN
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -89,6 +103,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
@@ -123,6 +139,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -155,6 +173,7 @@ github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@@ -192,8 +211,12 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -209,6 +232,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/koketama/errors v1.0.0 h1:4JO7SGSkBROkTuacd+cgPtBDr7fisixXoajkNh4eW3M=
github.com/koketama/errors v1.0.0/go.mod h1:SIU03UGMkhp6tyB5+EvhDI9i9ZDa4rQolJCt9ZrFrbU=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -220,6 +245,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -237,6 +264,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -306,8 +335,11 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A=
github.com/shirou/gopsutil v3.21.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -345,6 +377,10 @@ github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -363,6 +399,7 @@ github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUd
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -378,12 +415,15 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -406,9 +446,11 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -428,6 +470,9 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -442,9 +487,11 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -461,6 +508,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -469,8 +517,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341 h1:2/QtM1mL37YmcsT8HaDNHDgTqqFVw+zr8UzMiBVLzYU=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -508,6 +556,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a h1:pdfjQ7VswBeGam3EpuEJ4e8EAb7JgaubV570LO/SIQM=
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -575,6 +624,8 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek=
gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-5
View File
@@ -1,5 +0,0 @@
## init
项目初始脚本。
- DB 相关 SQL
-10
View File
@@ -1,10 +0,0 @@
CREATE TABLE `user_demo` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',
`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';
+4
View File
@@ -13,6 +13,8 @@ const (
ParamBindError = 10103
AuthorizationError = 10104
CallHTTPError = 10105
ResubmitError = 10106
ResubmitMsg = 10107
// 模块级错误码 - 用户模块
IllegalUserName = 20101
@@ -29,6 +31,8 @@ var codeText = map[int]string{
ParamBindError: "参数信息有误",
AuthorizationError: "签名信息有误",
CallHTTPError: "调用第三方 HTTP 接口失败",
ResubmitError: "Resubmit Error",
ResubmitMsg: "请勿重复提交",
IllegalUserName: "非法用户名",
UserCreateError: "创建用户失败",
@@ -28,7 +28,7 @@ type authResponse struct {
func (h *handler) Auth() core.HandlerFunc {
return func(c core.Context) {
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).Sign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
tokenString, err := token.New(cfg.Secret).JwtSign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
@@ -5,7 +5,7 @@ import (
"time"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/repository/third_party_request/go_gin_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/third_party_request/go_gin_api"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
@@ -34,12 +34,12 @@ type traceResponse []struct {
func (h *handler) Trace() core.HandlerFunc {
return func(c core.Context) {
// 三方请求信息
res1, err := go_gin_api_repo.DemoGet("Tom",
res1, err := go_gin_api.DemoGet("Tom",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoGetRetryVerify),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api.DemoGetRetryVerify),
)
if err != nil {
@@ -56,12 +56,12 @@ func (h *handler) Trace() core.HandlerFunc {
p.Println("res1.Name", res1.Name, p.WithTrace(c.Trace()))
// 三方请求信息
res2, err := go_gin_api_repo.DemoPost("Jack",
res2, err := go_gin_api.DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoPostRetryVerify),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api.DemoPostRetryVerify),
)
if err != nil {
@@ -19,9 +19,15 @@ type Handler interface {
Get() core.HandlerFunc
// 示例:支持 post 请求的方法
Post() core.HandlerFunc
// 获取授权信息
// @Tags Demo
// @Router /auth/get [post]
Auth() core.HandlerFunc
// Trace 示例
// @Tags Demo
// @Router /demo/trace [get]
Trace() core.HandlerFunc
}
@@ -18,7 +18,7 @@ type createRequest struct {
}
type createResponse struct {
Id uint `json:"id"` // 主键ID
Id int32 `json:"id"` // 主键ID
}
// 创建用户
@@ -9,16 +9,16 @@ import (
)
type deleteRequest struct {
Id uint `uri:"id"` // 用户ID
Id int32 `uri:"id"` // 用户ID
}
type deleteResponse struct {
Id uint `json:"id"` // 用户主键ID
Id int32 `json:"id"` // 用户主键ID
}
// 删除用户 - 更新 is_deleted = 1
// @Summary 删除用户 - 更新 is_deleted = 1
// @Description 删除用户 - 更新 is_deleted = 1
// 删除用户
// @Summary 删除用户
// @Description 删除用户
// @Tags User
// @Accept json
// @Produce json
@@ -14,7 +14,7 @@ type detailRequest struct {
}
type detailResponse struct {
Id uint `json:"id"` // 用户主键ID
Id int32 `json:"id"` // 用户主键ID
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile ddm.Mobile `json:"mobile"` // 手机号(脱敏)
@@ -9,12 +9,12 @@ import (
)
type updateNickNameByIDRequest struct {
Id uint `json:"id"` // 用户主键ID
Id int32 `json:"id"` // 用户主键ID
NickName string `json:"nick_name"` // 昵称
}
type updateNickNameByIDResponse struct {
Id uint `json:"id"` // 用户主键ID
Id int32 `json:"id"` // 用户主键ID
}
// 编辑用户 - 通过用户主键ID更新用户昵称
@@ -14,13 +14,25 @@ var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// Create 创建用户
// @Tags User
// @Router /user/create [post]
Create() core.HandlerFunc
// UpdateNickNameByID 编辑用户 - 通过主键ID更新用户昵称
// @Tags User
// @Router /user/update [put]
UpdateNickNameByID() core.HandlerFunc
// Delete 删除用户 - 通过主键ID更新 is_deleted = 1
// Delete 删除用户
// @Tags User
// @Router /user/delete/{id} [patch]
Delete() core.HandlerFunc
// Detail 用户详情
// @Tags User
// @Router /user/info/{username} [get]
Detail() core.HandlerFunc
}
+7 -11
View File
@@ -1,16 +1,11 @@
## repository
数据访问层。
#### 数据访问层。
- `./db_repo` 访问 DB 数据
- `./cache_repo` 访问 Cache 数据
- `./third_party_request` 访问外部 HTTP 接口数据。
SQL 建议:
- 禁止使用 SQL k v 拼接,好处是避免 SQL 注入;
- 禁止使用连表查询,好处是易扩展,比如分库分表;
- 禁止使用万能方法,好处是便于后期维护,比如字段调整;
- 禁止使用删除方法,好处是避免数据丢失;
#### SQL 建议:
- 建议每张表需包含字段:主键(id)、标记删除(is_deteled)、创建时间(created_at)、更新时间(updated_at)
```mysql
@@ -19,11 +14,12 @@ SQL 建议:
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
```
什么是万能方法?
指的是特别灵活的查询,比如通过非固定的参数返回全部字段,建议做到需要什么返回什么,不要返回大而全的数据,更新时也不能传递什么参数更新什么参数,更新字段要提前约定好。
命名规范:
#### 命名规范:
- 包名应以 `_repo` 结尾;
- `./db_repo` 目录下的包名以 `数据表名`+ `_repo` 命名;
#### 脚本生成 MySQL CURD
- 使用脚本自动生成基于表结构的 CURD 代码,见文档:`./cmd/gormgen/README.md`
+51
View File
@@ -0,0 +1,51 @@
## 使用示例
`user_demo` 为例:
```go
// 查询:多条 + 分页
page := 2
num := 2
offset := (page - 1) * num
user, err = user_demo_repo.NewQueryBuilder().
WhereIdNotIn([]int32{1, 2, 3}).
WhereUserName(db_repo.EqualPredicate, "tom").
Limit(num).
Offset(offset).
QueryAll(u.db.GetDbR().WithContext(ctx.RequestContext()))
// 查询:总数
count, err := user_demo_repo.NewQueryBuilder().
WhereIdNotIn([]int32{1, 2, 3}).
WhereUserName(db_repo.EqualPredicate, "tom").
Count(u.db.GetDbR().WithContext(ctx.RequestContext()))
// 查询:单条
user, err = user_demo_repo.NewQueryBuilder().
WhereUserName(db_repo.EqualPredicate, "tom").
QueryOne(u.db.GetDbR().WithContext(ctx.RequestContext()))
// 创建
model := user_demo_repo.NewModel()
model.UserName = user.UserName
model.NickName = user.NickName
model.Mobile = user.Mobile
id, err = model.Create(u.db.GetDbW().WithContext(ctx.RequestContext()))
// 编辑
model := user_demo_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"nick_name": nickname,
}
err = model.Updates(u.db.GetDbW().WithContext(ctx.RequestContext()), data)
// 删除
model := user_demo_repo.NewModel()
model.Id = id
err = model.Delete(u.db.GetDbW().WithContext(ctx.RequestContext()))
```
+14
View File
@@ -0,0 +1,14 @@
package db_repo
// Predicate is a string that acts as a condition in the where clause
type Predicate string
var (
EqualPredicate = Predicate("=")
NotEqualPredicate = Predicate("<>")
GreaterThanPredicate = Predicate(">")
GreaterThanOrEqualPredicate = Predicate(">=")
SmallerThanPredicate = Predicate("<")
SmallerThanOrEqualPredicate = Predicate("<=")
LikePredicate = Predicate("LIKE")
)
@@ -0,0 +1,15 @@
package user_demo_repo
import "time"
// 用户Demo表
//go:generate gormgen -structs UserDemo -input .
type UserDemo struct {
Id int32 // 主键
UserName string // 用户名
NickName string // 昵称
Mobile string // 手机号
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
UpdatedAt time.Time `gorm:"time"` // 更新时间
}
@@ -0,0 +1,12 @@
#### go_gin_api.user_demo
用户Demo表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | user_name | 用户名 | varchar(32) | | NO | | |
| 3 | nick_name | 昵称 | varchar(100) | | NO | | |
| 4 | mobile | 手机号 | varchar(20) | | NO | | |
| 5 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 6 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 7 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
@@ -0,0 +1,411 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package user_demo_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *UserDemo {
return new(UserDemo)
}
func NewQueryBuilder() *userDemoRepoQueryBuilder {
return new(userDemoRepoQueryBuilder)
}
func (t *UserDemo) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *UserDemo) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *UserDemo) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type userDemoRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *userDemoRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *userDemoRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&UserDemo{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *userDemoRepoQueryBuilder) First(db *gorm.DB) (*UserDemo, error) {
ret := &UserDemo{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *userDemoRepoQueryBuilder) QueryOne(db *gorm.DB) (*UserDemo, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *userDemoRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*UserDemo, error) {
var ret []*UserDemo
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *userDemoRepoQueryBuilder) Limit(limit int) *userDemoRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *userDemoRepoQueryBuilder) Offset(offset int) *userDemoRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIdIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIdNotIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderById(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserNameIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserNameNotIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUserName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "user_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickNameIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickNameNotIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByNickName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "nick_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobileIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobileNotIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByMobile(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "mobile "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeletedIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeletedNotIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByIsDeleted(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAtIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAtNotIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByCreatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAtIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAtNotIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUpdatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}
@@ -1,20 +0,0 @@
package user_demo_repo
import (
"time"
)
// 用户Demo表
type UserDemo struct {
Id uint `gorm:"column:id;primary_key;AUTO_INCREMENT"` // 主键
UserName string `gorm:"column:user_name;NOT NULL"` // 用户名
NickName string `gorm:"column:nick_name;NOT NULL"` // 昵称
Mobile string `gorm:"column:mobile;NOT NULL"` // 手机号
IsDeleted int `gorm:"column:is_deleted;default:-1;NOT NULL"` // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 创建时间
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 更新时间
}
func (m *UserDemo) TableName() string {
return "user_demo"
}
@@ -1,80 +0,0 @@
package user_demo_repo
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/pkg/errors"
"gorm.io/gorm"
)
var _ UserRepo = (*userRepo)(nil)
type UserRepo interface {
// i 为了避免被其他包实现
i()
Create(ctx core.Context, user UserDemo) (id uint, err error)
UpdateNickNameByID(ctx core.Context, id uint, username string) (err error)
GetUserByUserName(ctx core.Context, username string) (*UserDemo, error)
Delete(ctx core.Context, id uint) (err error)
getUserByID(ctx core.Context, id uint) (*UserDemo, error)
}
type userRepo struct {
db db.Repo
}
func NewUserRepo(db db.Repo) UserRepo {
return &userRepo{
db: db,
}
}
func (u *userRepo) i() {}
func (u *userRepo) Create(ctx core.Context, user UserDemo) (id uint, err error) {
err = u.db.GetDbW().WithContext(ctx.RequestContext()).Create(&user).Error
if err != nil {
return 0, errors.Wrap(err, "[user_repo] create user err")
}
return user.Id, nil
}
func (u *userRepo) getUserByID(ctx core.Context, id uint) (*UserDemo, error) {
data := new(UserDemo)
err := u.db.GetDbR().
WithContext(ctx.RequestContext()).First(data, id).Where("is_deleted = ?", -1).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(err, "[user_demo] get user data err")
}
return data, nil
}
func (u *userRepo) UpdateNickNameByID(ctx core.Context, id uint, nickname string) (err error) {
user, err := u.getUserByID(ctx, id)
if err != nil {
return errors.Wrap(err, "[user_demo] get user data err")
}
return u.db.GetDbW().WithContext(ctx.RequestContext()).Model(user).Update("nick_name", nickname).Error
}
func (u *userRepo) Delete(ctx core.Context, id uint) (err error) {
user, err := u.getUserByID(ctx, id)
if err != nil {
return errors.Wrap(err, "[user_demo] get user data err")
}
return u.db.GetDbW().WithContext(ctx.RequestContext()).Model(user).Update("is_deleted", 1).Error
}
func (u *userRepo) GetUserByUserName(ctx core.Context, username string) (*UserDemo, error) {
data := new(UserDemo)
err := u.db.GetDbR().
WithContext(ctx.RequestContext()).
Select([]string{"id", "user_name", "nick_name", "mobile"}).
Where("user_name = ? and is_deleted = ?", username, -1).
First(data).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(err, "[user_demo] get user data err")
}
return data, nil
}
@@ -1,21 +0,0 @@
package go_gin_api_repo
import "encoding/json"
func MockDemoGet() (body []byte) {
res := new(demoGetResponse)
res.Name = "AA"
res.Job = "AA_JOB"
body, _ = json.Marshal(res)
return body
}
func MockDemoPost() (body []byte) {
res := new(demoPostResponse)
res.Name = "BB"
res.Job = "BB_JOB"
body, _ = json.Marshal(res)
return body
}
@@ -1,80 +0,0 @@
package go_gin_api_repo
import (
"encoding/json"
"testing"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/third_party_request"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
)
var authorization = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOjEsIlVzZXJOYW1lIjoieGlubGlhbmdub3RlIiwiZXhwIjoxNjEzODI3MTEzLCJpYXQiOjE2MTM3NDA3MTMsIm5iZiI6MTYxMzc0MDcxM30.SnooP1ikO33ryGPdohsmOKqISa-bWzMkMvUNb5f2zc0"
func TestDemoGet(t *testing.T) {
res, err := DemoGet("Tom",
httpclient.WithTTL(time.Second*5),
//httpclient.WithTrace(ctx.Trace()),
//httpclient.WithLogger(ctx.Logger()),
httpclient.WithHeader("Authorization", authorization),
httpclient.WithOnFailedRetry(3, time.Second*1, retryVerify),
httpclient.WithOnFailedAlarm("接口告警", new(third_party_request.AlarmEmail), alarmVerify),
//httpclient.WithMock(MockDemoGet),
)
if err != nil {
t.Log("get [demo/get] err", err)
}
t.Log(res)
}
func TestDemoPost(t *testing.T) {
res, err := DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
//httpclient.WithTrace(ctx.Trace()),
//httpclient.WithLogger(ctx.Logger()),
httpclient.WithHeader("Authorization", authorization),
httpclient.WithMock(MockDemoPost),
)
if err != nil {
t.Log("post [demo/post] err", err)
}
t.Log(res)
}
// 设置重试规则
func retryVerify(body []byte) (shouldRetry bool) {
if len(body) == 0 {
return true
}
type Response struct {
Code int `json:"code"`
}
resp := new(Response)
if err := json.Unmarshal(body, resp); err != nil {
return true
}
return resp.Code != 1
}
// 设置告警规则
func alarmVerify(body []byte) (shouldAlarm bool) {
if len(body) == 0 {
return true
}
type Response struct {
Code int `json:"code"`
}
resp := new(Response)
if err := json.Unmarshal(body, resp); err != nil {
return true
}
return resp.Code != 1
}
-74
View File
@@ -1,74 +0,0 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler"
"github.com/xinliangnote/go-gin-api/internal/api/router/middleware/auth"
"github.com/xinliangnote/go-gin-api/internal/graph/handler"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
"github.com/pkg/errors"
"go.uber.org/zap"
)
func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) (core.Mux, error) {
if logger == nil {
return nil, errors.New("logger required")
}
mux, err := core.New(logger,
core.WithEnableCors(),
core.WithEnableRate(),
core.WithPanicNotify(notify.OnPanicNotify),
core.WithRecordMetrics(metrics.RecordMetrics),
)
if err != nil {
panic(err)
}
gqlHandler := handler.New(logger, db, cache)
gql := mux.Group("/graphql")
{
gql.GET("", gqlHandler.Playground())
gql.POST("/query", gqlHandler.Query())
}
demoHandler := demo_handler.New(logger, db, cache, grpConn)
userHandler := user_handler.New(logger, db, cache)
// user_demo CURD
user := mux.Group("/user", core.WrapAuthHandler(auth.AuthHandler))
{
user.POST("/create", userHandler.Create())
user.PUT("/update", userHandler.UpdateNickNameByID())
user.PATCH("/delete/:id", userHandler.Delete())
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
}
// auth
a := mux.Group("/auth")
{
a.POST("/get", demoHandler.Auth())
}
// demo
d := mux.Group("/demo", core.WrapAuthHandler(auth.AuthHandler)) //使用 auth 验证
{
// 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。
d.GET("/trace", demoHandler.Trace())
// 模拟数据
d.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get())
d.POST("post", demoHandler.Post())
}
return mux, nil
}
@@ -1,11 +0,0 @@
package user_service
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (u *userSer) Delete(ctx core.Context, id uint) (err error) {
err = u.userRepo.Delete(ctx, id)
if err != nil {
return nil
}
return nil
}
@@ -1,11 +0,0 @@
package user_service
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (u *userSer) UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) {
err = u.userRepo.UpdateNickNameByID(ctx, id, username)
if err != nil {
return nil
}
return nil
}
+7 -10
View File
@@ -13,24 +13,21 @@ type UserService interface {
// i 为了避免被其他包实现
i()
Create(ctx core.Context, user *CreateUserInfo) (id uint, err error)
UpdateNickNameByID(ctx core.Context, id uint, username string) (err error)
Create(ctx core.Context, user *CreateUserInfo) (id int32, err error)
UpdateNickNameByID(ctx core.Context, id int32, username string) (err error)
GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error)
Delete(ctx core.Context, id uint) (err error)
Delete(ctx core.Context, id int32) (err error)
}
type userSer struct {
db db.Repo
cache cache.Repo
userRepo user_demo_repo.UserRepo
db db.Repo
cache cache.Repo
}
func NewUserService(db db.Repo, cache cache.Repo) UserService {
userRepo := user_demo_repo.NewUserRepo(db)
return &userSer{
db: db,
cache: cache,
userRepo: userRepo,
db: db,
cache: cache,
}
}
@@ -11,14 +11,13 @@ type CreateUserInfo struct {
Mobile string `json:"mobile"` // 手机号
}
func (u *userSer) Create(ctx core.Context, user *CreateUserInfo) (id uint, err error) {
create := user_demo_repo.UserDemo{
UserName: user.UserName,
NickName: user.NickName,
Mobile: user.Mobile,
}
func (u *userSer) Create(ctx core.Context, user *CreateUserInfo) (id int32, err error) {
model := user_demo_repo.NewModel()
model.UserName = user.UserName
model.NickName = user.NickName
model.Mobile = user.Mobile
id, err = u.userRepo.Create(ctx, create)
id, err = model.Create(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
@@ -0,0 +1,16 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) Delete(ctx core.Context, id int32) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
err = model.Delete(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return nil
}
return nil
}
@@ -1,14 +1,23 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error) {
user, err = u.userRepo.GetUserByUserName(ctx, username)
user, err = user_demo_repo.NewQueryBuilder().
WhereUserName(db_repo.EqualPredicate, username).
QueryOne(u.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
return user, err
}
if user == nil {
user = user_demo_repo.NewModel()
}
return user, nil
}
@@ -0,0 +1,22 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) UpdateNickNameByID(ctx core.Context, id int32, nickname string) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"nick_name": nickname,
}
err = model.Updates(u.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
return nil
}
@@ -1,8 +1,11 @@
package third_party_request
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/xinliangnote/go-gin-api/pkg/mail"
"github.com/pkg/errors"
)
// 实现 AlarmObject 告警
@@ -10,15 +13,22 @@ var _ httpclient.AlarmObject = (*AlarmEmail)(nil)
type AlarmEmail struct{}
// 邮件告警方式
func (a *AlarmEmail) Send(subject, body string) error {
cfg := configs.Get().Mail
if cfg.Host == "" || cfg.Port == 0 || cfg.User == "" || cfg.Pass == "" || cfg.To == "" {
return errors.New("mail config error")
}
options := &mail.Options{
MailHost: "smtp.163.com",
MailPort: 465,
MailUser: "xx@163.com",
MailPass: "",
MailTo: "",
MailHost: cfg.Host,
MailPort: cfg.Port,
MailUser: cfg.User,
MailPass: cfg.Pass,
MailTo: cfg.To,
Subject: subject,
Body: body,
}
return mail.Send(options)
}
@@ -0,0 +1,63 @@
package go_gin_api
import (
"encoding/json"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/pkg/errors"
)
// 接口地址
var demoGetApi = "http://127.0.0.1:9999/demo/get/"
// 接口返回结构
type demoGetResponse struct {
Name string `json:"name"`
Job string `json:"job"`
}
// 发起请求
func DemoGet(name string, opts ...httpclient.Option) (res *demoGetResponse, err error) {
api := demoGetApi + name
body, err := httpclient.Get(api, nil, opts...)
if err != nil {
return nil, err
}
res = new(demoGetResponse)
err = json.Unmarshal(body, res)
if err != nil {
return nil, errors.Wrap(err, "DemoGet json unmarshal error")
}
return res, nil
}
// 设置重试规则
func DemoGetRetryVerify(body []byte) (shouldRetry bool) {
if len(body) == 0 {
return true
}
return false
}
// 设置告警规则
func DemoGetAlarmVerify(body []byte) (shouldAlarm bool) {
if len(body) == 0 {
return true
}
return false
}
// 设置 Mock 数据
func DemoGetMock() (body []byte) {
res := new(demoGetResponse)
res.Name = "AA"
res.Job = "AA_JOB"
body, _ = json.Marshal(res)
return body
}
@@ -0,0 +1,29 @@
package go_gin_api
import (
"testing"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/third_party_request"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
)
var demoGetAuthorization = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOjEsIlVzZXJOYW1lIjoieGlubGlhbmdub3RlIiwiZXhwIjoxNjEzODI3MTEzLCJpYXQiOjE2MTM3NDA3MTMsIm5iZiI6MTYxMzc0MDcxM30.SnooP1ikO33ryGPdohsmOKqISa-bWzMkMvUNb5f2zc0"
func TestDemoGet(t *testing.T) {
res, err := DemoGet("Tom",
httpclient.WithTTL(time.Second*5),
//httpclient.WithTrace(ctx.Trace()),
//httpclient.WithLogger(ctx.Logger()),
httpclient.WithHeader("Authorization", demoGetAuthorization),
httpclient.WithOnFailedRetry(3, time.Second*1, DemoGetRetryVerify),
httpclient.WithOnFailedAlarm("接口告警", new(third_party_request.AlarmEmail), DemoGetAlarmVerify),
httpclient.WithMock(DemoGetMock),
)
if err != nil {
t.Log("get [demo/get] err", err)
}
t.Log(res)
}
@@ -1,4 +1,4 @@
package go_gin_api_repo
package go_gin_api
import (
"encoding/json"
@@ -9,42 +9,18 @@ import (
"github.com/pkg/errors"
)
type demoGetResponse struct {
Name string `json:"name"`
Job string `json:"job"`
}
// 接口地址
var demoPostApi = "http://127.0.0.1:9999/demo/post/"
// 接口返回结构
type demoPostResponse struct {
Name string `json:"name"`
Job string `json:"job"`
}
func DemoGet(name string, opts ...httpclient.Option) (res *demoGetResponse, err error) {
api := "http://127.0.0.1:9999/demo/get/" + name
body, err := httpclient.Get(api, nil, opts...)
if err != nil {
return nil, err
}
res = new(demoGetResponse)
err = json.Unmarshal(body, res)
if err != nil {
return nil, errors.Wrap(err, "DemoGet json unmarshal error")
}
return res, nil
}
func DemoGetRetryVerify(body []byte) (shouldRetry bool) {
if len(body) == 0 {
return true
}
return false
}
// 发起请求
func DemoPost(name string, opts ...httpclient.Option) (res *demoPostResponse, err error) {
api := "http://127.0.0.1:9999/demo/post"
api := demoPostApi
params := url.Values{}
params.Set("name", name)
body, err := httpclient.PostForm(api, params, opts...)
@@ -61,6 +37,7 @@ func DemoPost(name string, opts ...httpclient.Option) (res *demoPostResponse, er
return res, nil
}
// 设置重试规则
func DemoPostRetryVerify(body []byte) (shouldRetry bool) {
if len(body) == 0 {
return true
@@ -68,3 +45,22 @@ func DemoPostRetryVerify(body []byte) (shouldRetry bool) {
return false
}
// 设置告警规则
func DemoPostAlarmVerify(body []byte) (shouldAlarm bool) {
if len(body) == 0 {
return true
}
return false
}
// 设置 Mock 数据
func DemoPostMock() (body []byte) {
res := new(demoPostResponse)
res.Name = "BB"
res.Job = "BB_JOB"
body, _ = json.Marshal(res)
return body
}
@@ -0,0 +1,29 @@
package go_gin_api
import (
"testing"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/third_party_request"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
)
var demoPostAuthorization = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOjEsIlVzZXJOYW1lIjoieGlubGlhbmdub3RlIiwiZXhwIjoxNjEzODI3MTEzLCJpYXQiOjE2MTM3NDA3MTMsIm5iZiI6MTYxMzc0MDcxM30.SnooP1ikO33ryGPdohsmOKqISa-bWzMkMvUNb5f2zc0"
func TestDemoPost(t *testing.T) {
res, err := DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
//httpclient.WithTrace(ctx.Trace()),
//httpclient.WithLogger(ctx.Logger()),
httpclient.WithHeader("Authorization", demoPostAuthorization),
httpclient.WithOnFailedRetry(3, time.Second*1, DemoPostRetryVerify),
httpclient.WithOnFailedAlarm("接口告警", new(third_party_request.AlarmEmail), DemoPostAlarmVerify),
httpclient.WithMock(DemoPostMock),
)
if err != nil {
t.Log("post [demo/post] err", err)
}
t.Log(res)
}
+10
View File
@@ -34,6 +34,7 @@ type Repo interface {
Expire(key string, ttl time.Duration) bool
ExpireAt(key string, ttl time.Time) bool
Del(keys ...string) bool
Exists(keys ...string) bool
Incr(key string, options ...Option) int64
Close() error
}
@@ -148,6 +149,15 @@ func (c *cacheRepo) ExpireAt(key string, ttl time.Time) bool {
return ok
}
//
func (c *cacheRepo) Exists(keys ...string) bool {
if len(keys) == 0 {
return true
}
value, _ := c.client.Exists(keys...).Result()
return value > 0
}
// Del del some key from redis
func (c *cacheRepo) Del(keys ...string) bool {
if len(keys) == 0 {
+23
View File
@@ -97,6 +97,9 @@ type Context interface {
GraphPayload(payload interface{})
getGraphPayload() interface{}
// HTML 返回界面
HTML(name string, obj interface{})
// AbortWithError 错误返回
AbortWithError(err errno.Error)
abortError() errno.Error
@@ -120,6 +123,10 @@ type Context interface {
Alias() string
setAlias(path string)
// RequestInputParams 获取所有参数
RequestInputParams() url.Values
// RequestPostFormParams 获取 PostForm 参数
RequestPostFormParams() url.Values
// Request 获取 Request 对象
Request() *http.Request
// RawData 获取 Request.Body
@@ -247,6 +254,10 @@ func (c *context) GraphPayload(payload interface{}) {
c.ctx.Set(_GraphPayloadName, payload)
}
func (c *context) HTML(name string, obj interface{}) {
c.ctx.HTML(200, name+".html", obj)
}
func (c *context) Header() http.Header {
header := c.ctx.Request.Header
@@ -326,6 +337,18 @@ func (c *context) setAlias(path string) {
}
}
// RequestInputParams 获取所有参数
func (c *context) RequestInputParams() url.Values {
_ = c.ctx.Request.ParseForm()
return c.ctx.Request.Form
}
// RequestPostFormParams 获取 PostForm 参数
func (c *context) RequestPostFormParams() url.Values {
_ = c.ctx.Request.ParseForm()
return c.ctx.Request.PostForm
}
// Request 获取 Request
func (c *context) Request() *http.Request {
return c.ctx.Request
+11 -4
View File
@@ -246,6 +246,9 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
fmt.Println(color.Green(fmt.Sprintf("* [register port %s]", configs.ProjectPort())))
fmt.Println(color.Green(fmt.Sprintf("* [register env %s]", env.Active().Value())))
mux.engine.StaticFS("bootstrap", http.Dir("./assets/bootstrap"))
mux.engine.LoadHTMLGlob("./assets/templates/**/*")
// withoutLogPaths 这些请求,默认不记录日志
withoutTracePaths := map[string]bool{
"/metrics": true,
@@ -273,13 +276,17 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}
if !opt.disablePProf {
pprof.Register(mux.engine) // register pprof to gin
fmt.Println(color.Green("* [register pprof]"))
if !env.Active().IsPro() {
pprof.Register(mux.engine) // register pprof to gin
fmt.Println(color.Green("* [register pprof]"))
}
}
if !opt.disableSwagger {
mux.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // register swagger
fmt.Println(color.Green("* [register swagger]"))
if !env.Active().IsPro() {
mux.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // register swagger
fmt.Println(color.Green("* [register swagger]"))
}
}
if !opt.disablePrometheus {
+4
View File
@@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
var _ Repo = (*dbRepo)(nil)
@@ -80,6 +81,9 @@ func dbConnect(user, pass, addr, dbName string) (*gorm.DB, error) {
"Local")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
})
@@ -0,0 +1,9 @@
package middleware
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (m *middleware) DisableLog() core.HandlerFunc {
return func(c core.Context) {
core.DisableTrace(c)
}
}
@@ -1,4 +1,4 @@
package auth
package middleware
import (
"net/http"
@@ -12,7 +12,7 @@ import (
"github.com/pkg/errors"
)
func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Error) {
func (m *middleware) Jwt(ctx core.Context) (userId int64, userName string, err errno.Error) {
auth := ctx.GetHeader("Authorization")
if auth == "" {
err = errno.NewError(
@@ -24,7 +24,7 @@ func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Err
}
cfg := configs.Get().JWT
claims, errParse := token.New(cfg.Secret).Parse(auth)
claims, errParse := token.New(cfg.Secret).JwtParse(auth)
if errParse != nil {
err = errno.NewError(
http.StatusUnauthorized,
@@ -0,0 +1,70 @@
package middleware
import (
"net/http"
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/token"
"github.com/pkg/errors"
)
func (m *middleware) Resubmit() core.HandlerFunc {
redisKeyPrefix := configs.ProjectName() + ":request-id:"
return func(c core.Context) {
cfg := configs.Get().URLToken
tokenString, err := token.New(cfg.Secret).UrlSign(c.Path(), c.Method(), c.RequestInputParams())
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitError,
code.Text(code.ResubmitError)).WithErr(err),
)
return
}
redisKey := redisKeyPrefix + tokenString
if !m.cache.Exists(redisKey) {
err = m.cache.Set(redisKey, "1", time.Minute*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitError,
code.Text(code.ResubmitError)).WithErr(err),
)
return
}
return
}
redisValue, err := m.cache.Get(redisKey, cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitError,
code.Text(code.ResubmitError)).WithErr(err),
)
return
}
if redisValue == "1" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitMsg,
code.Text(code.ResubmitMsg)).WithErr(errors.New("resubmit")),
)
return
}
return
}
}
+39
View File
@@ -0,0 +1,39 @@
package middleware
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"go.uber.org/zap"
)
var _ Middleware = (*middleware)(nil)
type Middleware interface {
// i 为了避免被其他包实现
i()
// JWT 中间件
Jwt(ctx core.Context) (userId int64, userName string, err errno.Error)
// Resubmit 中间件
Resubmit() core.HandlerFunc
// DisableLog 不记录日志
DisableLog() core.HandlerFunc
}
type middleware struct {
logger *zap.Logger
cache cache.Repo
}
func New(logger *zap.Logger, cache cache.Repo) Middleware {
return &middleware{
logger: logger,
cache: cache,
}
}
func (m *middleware) i() {}
+60
View File
@@ -0,0 +1,60 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
"github.com/xinliangnote/go-gin-api/internal/router/middleware"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type resource struct {
mux core.Mux
logger *zap.Logger
db db.Repo
cache cache.Repo
grpConn grpc.ClientConn
middles middleware.Middleware
}
func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) (core.Mux, error) {
if logger == nil {
return nil, errors.New("logger required")
}
mux, err := core.New(logger,
core.WithEnableCors(),
core.WithEnableRate(),
core.WithPanicNotify(notify.OnPanicNotify),
core.WithRecordMetrics(metrics.RecordMetrics),
)
if err != nil {
panic(err)
}
r := new(resource)
r.mux = mux
r.logger = logger
r.db = db
r.cache = cache
r.grpConn = grpConn
r.middles = middleware.New(logger, cache)
// 设置 WEB 路由
setWebRouter(r)
// 设置 API 路由
setApiRouter(r)
// 设置 GraphQL 路由
setGraphQLRouter(r)
return mux, nil
}
+36
View File
@@ -0,0 +1,36 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func setApiRouter(r *resource) {
// demo 控制器
demoHandler := demo_handler.New(r.logger, r.db, r.cache, r.grpConn)
demo := r.mux.Group("/demo", core.WrapAuthHandler(r.middles.Jwt)) // 使用 jwt 验证
{
// 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。
demo.GET("/trace", demoHandler.Trace())
// 模拟数据
demo.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get())
demo.POST("post", demoHandler.Post())
}
demoNoAuth := r.mux.Group("/auth") // 不使用 jwt 验证
{
demoNoAuth.POST("/get", demoHandler.Auth())
}
// user 控制器
userHandler := user_handler.New(r.logger, r.db, r.cache)
user := r.mux.Group("/user", core.WrapAuthHandler(r.middles.Jwt))
{
user.POST("/create", userHandler.Create())
user.PUT("/update", userHandler.UpdateNickNameByID())
user.PATCH("/delete/:id", userHandler.Delete())
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
}
}
+14
View File
@@ -0,0 +1,14 @@
package router
import "github.com/xinliangnote/go-gin-api/internal/graph/handler"
func setGraphQLRouter(r *resource) {
// graphQL 控制器
gqlHandler := handler.New(r.logger, r.db, r.cache)
gql := r.mux.Group("/graphql")
{
gql.GET("", gqlHandler.Playground())
gql.POST("/query", gqlHandler.Query())
}
}
+39
View File
@@ -0,0 +1,39 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/web/controller/configinfo_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/dashboard_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/gencode_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/index_handler"
)
func setWebRouter(r *resource) {
indexHandler := index_handler.New(r.logger, r.db, r.cache)
dashboardHandler := dashboard_handler.New(r.logger, r.db, r.cache)
genCodeHandler := gencode_handler.New(r.logger, r.db, r.cache)
configInfoHandler := configinfo_handler.New(r.logger, r.db, r.cache)
web := r.mux.Group("", r.middles.DisableLog())
{
// 首页侧边栏
web.GET("", indexHandler.View())
// 仪表盘
web.GET("/dashboard", dashboardHandler.View())
// 配置信息
web.GET("/configinfo", configInfoHandler.View())
// 代码生成
web.GET("/init", genCodeHandler.InitView())
web.POST("/init_exec", genCodeHandler.InitExecute())
web.GET("/gormgen", genCodeHandler.GormView())
web.POST("/gormgen_exec", genCodeHandler.GormExecute())
web.GET("/handlergen", genCodeHandler.HandlerView())
web.POST("/handlergen_exec", genCodeHandler.HandlerExecute())
}
}
@@ -0,0 +1,12 @@
package configinfo_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (h *handler) View() core.HandlerFunc {
return func(c core.Context) {
c.HTML("configinfo", configs.Get())
}
}
@@ -0,0 +1,31 @@
package configinfo_handler
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
i()
View() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
}
}
func (h *handler) i() {}

Some files were not shown because too many files have changed in this diff Show More