mirror of
https://github.com/xinliangnote/go-gin-api.git
synced 2024-04-21 12:31:46 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e918fce59 | ||
|
|
36ef904211 | ||
|
|
49ccaf9bec | ||
|
|
4381a9a6f6 | ||
|
|
5e5db8917d | ||
|
|
f7b3dbae5e | ||
|
|
5d7975056c | ||
|
|
499e171d63 | ||
|
|
8d6cd2174e |
@@ -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)
|
||||
|
||||
|
||||
## 文档索引
|
||||
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+4303
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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
+2
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+134
@@ -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
@@ -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))}});
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
Vendored
+141
@@ -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
Vendored
+4
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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
+11
-2
@@ -1,6 +1,15 @@
|
||||
## 执行命令
|
||||
1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割;
|
||||
1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`;
|
||||
在根目录下执行脚本:`./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
|
||||
@@ -3,6 +3,7 @@ package pkg
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -119,6 +120,7 @@ func (g *Generator) Flush() error {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
|
||||
optionField fieldConfig
|
||||
)
|
||||
|
||||
// type is ident, get onlyField type
|
||||
if t, _ok := v.Type.(*ast.Ident); _ok {
|
||||
optionField.FieldType = t.String()
|
||||
} else {
|
||||
@@ -89,7 +88,6 @@ func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// get file name
|
||||
if len(v.Names) > 0 {
|
||||
optionField.FieldName = v.Names[0].String()
|
||||
optionField.ColumnName = gorm.ToDBName(optionField.FieldName)
|
||||
|
||||
@@ -52,7 +52,7 @@ func (t *{{.StructName}}) Delete(db *gorm.DB) (err error) {
|
||||
}
|
||||
|
||||
func (t *{{.StructName}}) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
|
||||
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
|
||||
if err = db.Model(&{{.StructName}}{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
|
||||
return errors.Wrap(err, "updates err")
|
||||
}
|
||||
return nil
|
||||
@@ -136,6 +136,28 @@ func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}(p d
|
||||
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 {
|
||||
|
||||
@@ -3,4 +3,100 @@
|
||||
```$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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
+34
-10
@@ -3,13 +3,15 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/dave/dst"
|
||||
"github.com/dave/dst/decorator"
|
||||
)
|
||||
|
||||
var handlerName string
|
||||
@@ -23,26 +25,31 @@ func init() {
|
||||
|
||||
func main() {
|
||||
fs := token.NewFileSet()
|
||||
file := fmt.Sprintf("./internal/api/controller/%s/handler.go", handlerName)
|
||||
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
|
||||
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", file, err)
|
||||
log.Fatalf("parsing package: %s: %s\n", filePath, err)
|
||||
}
|
||||
|
||||
ast.Inspect(parsedFile, func(n ast.Node) bool {
|
||||
decl, ok := n.(*ast.GenDecl)
|
||||
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.(*ast.TypeSpec)
|
||||
typeSpec, _ok := spec.(*dst.TypeSpec)
|
||||
if !_ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var interfaceType *ast.InterfaceType
|
||||
if interfaceType, ok = typeSpec.Type.(*ast.InterfaceType); !ok {
|
||||
var interfaceType *dst.InterfaceType
|
||||
if interfaceType, ok = typeSpec.Type.(*dst.InterfaceType); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -58,6 +65,7 @@ func main() {
|
||||
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"
|
||||
@@ -65,6 +73,22 @@ func main() {
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+39
-31
@@ -2,17 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/env"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/logger"
|
||||
"github.com/xinliangnote/go-gin-api/cmd/mysqlmd/mysql"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -33,39 +31,46 @@ type tableColumn struct {
|
||||
ColumnDefault sql.NullString `db:"COLUMN_DEFAULT"` // default value
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 初始化 logger
|
||||
loggers, err := logger.NewJSONLogger(
|
||||
logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName(), env.Active().Value())),
|
||||
logger.WithTimeLayout("2006-01-02 15:04:05"),
|
||||
logger.WithFileP(configs.ProjectLogFile()),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer loggers.Sync()
|
||||
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
|
||||
dbRepo, err := db.New()
|
||||
db, err := mysql.New(dbAddr, dbUser, dbPass, dbName)
|
||||
if err != nil {
|
||||
loggers.Fatal("new db err", zap.Error(err))
|
||||
log.Fatal("new db err", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := dbRepo.DbWClose(); err != nil {
|
||||
loggers.Error("dbw close err", zap.Error(err))
|
||||
}
|
||||
|
||||
if err := dbRepo.DbRClose(); err != nil {
|
||||
loggers.Error("dbr close err", zap.Error(err))
|
||||
if err := db.DbClose(); err != nil {
|
||||
log.Println("db close err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
dbName := configs.Get().MySQL.Read.Name
|
||||
genTables := configs.Get().Cmd.GenTables
|
||||
tables, err := queryTables(dbRepo.GetDbR(), dbName, genTables)
|
||||
tables, err := queryTables(db.GetDb(), dbName, genTables)
|
||||
if err != nil {
|
||||
loggers.Error("query tables of database err", zap.Error(err))
|
||||
log.Println("query tables of database err", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -73,13 +78,15 @@ func main() {
|
||||
|
||||
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("create and open markdown file error %v\n", err.Error())
|
||||
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)
|
||||
@@ -87,6 +94,7 @@ func main() {
|
||||
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"`)
|
||||
@@ -102,7 +110,7 @@ func main() {
|
||||
"| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |\n" +
|
||||
"| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n"
|
||||
|
||||
columnInfo, columnInfoErr := queryTableColumn(dbRepo.GetDbR(), dbName, table.Name)
|
||||
columnInfo, columnInfoErr := queryTableColumn(db.GetDb(), dbName, table.Name)
|
||||
if columnInfoErr != nil {
|
||||
continue
|
||||
}
|
||||
@@ -163,7 +171,7 @@ func queryTables(db *gorm.DB, dbName string, tableName string) ([]tableInfo, err
|
||||
}
|
||||
|
||||
// filter tables when specified tables params
|
||||
if tableName != "" {
|
||||
if tableName != "*" {
|
||||
tableCollect = nil
|
||||
chooseTables := strings.Split(tableName, ",")
|
||||
indexMap := make(map[int]int)
|
||||
|
||||
@@ -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
-13
@@ -54,19 +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"`
|
||||
|
||||
Cmd struct {
|
||||
GenTables string `toml:"genTables"`
|
||||
} `toml:"cmd"`
|
||||
URLToken struct {
|
||||
Secret string `toml:"secret"`
|
||||
ExpireDuration time.Duration `toml:"expireDuration"`
|
||||
} `toml:"urlToken"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -34,48 +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-----'
|
||||
|
||||
[cmd]
|
||||
genTables = 'user_demo'
|
||||
[urlToken]
|
||||
secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret
|
||||
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)
|
||||
@@ -4,32 +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
|
||||
|
||||
@@ -25,6 +25,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
|
||||
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=
|
||||
@@ -57,6 +59,12 @@ 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=
|
||||
@@ -95,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=
|
||||
@@ -163,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=
|
||||
@@ -200,6 +211,7 @@ 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=
|
||||
@@ -220,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=
|
||||
@@ -321,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=
|
||||
@@ -360,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=
|
||||
@@ -378,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=
|
||||
@@ -393,6 +415,7 @@ 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=
|
||||
@@ -423,6 +446,7 @@ 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=
|
||||
@@ -447,6 +471,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
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=
|
||||
@@ -462,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=
|
||||
@@ -481,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=
|
||||
@@ -489,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=
|
||||
@@ -528,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=
|
||||
@@ -595,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=
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
## init
|
||||
|
||||
项目初始脚本。
|
||||
|
||||
- DB 相关 SQL;
|
||||
@@ -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表';
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 删除用户
|
||||
// @Tags User
|
||||
// @Router /user/delete/{id} [patch]
|
||||
Delete() core.HandlerFunc
|
||||
|
||||
// Detail 用户详情
|
||||
// @Tags User
|
||||
// @Router /user/info/{username} [get]
|
||||
Detail() core.HandlerFunc
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
- `./db_repo` 访问 DB 数据
|
||||
- `./cache_repo` 访问 Cache 数据
|
||||
- `./third_party_request` 访问外部 HTTP 接口数据。
|
||||
|
||||
#### SQL 建议:
|
||||
- 建议每张表需包含字段:主键(id)、标记删除(is_deteled)、创建时间(created_at)、更新时间(updated_at)
|
||||
@@ -23,10 +22,4 @@
|
||||
|
||||
#### 脚本生成 MySQL CURD
|
||||
|
||||
1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割;
|
||||
1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`;
|
||||
|
||||
以用户表(user_demo)为例:
|
||||
- 结构体文件:user_demo_repo/gen_model.go;
|
||||
- CURD 方法文件:user_demo_repo/gen_user_demo.go;
|
||||
- 表结构 MD 文件:user_demo_repo/gen_table.md;
|
||||
- 使用脚本自动生成基于表结构的 CURD 代码,见文档:`./cmd/gormgen/README.md`
|
||||
|
||||
@@ -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()))
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
#### xin_ceshi.user_demo
|
||||
#### go_gin_api.user_demo
|
||||
用户Demo表
|
||||
|
||||
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
|
||||
|
||||
@@ -120,6 +120,28 @@ func (qb *userDemoRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *u
|
||||
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 {
|
||||
@@ -141,6 +163,28 @@ func (qb *userDemoRepoQueryBuilder) WhereUserName(p db_repo.Predicate, value str
|
||||
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 {
|
||||
@@ -162,6 +206,28 @@ func (qb *userDemoRepoQueryBuilder) WhereNickName(p db_repo.Predicate, value str
|
||||
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 {
|
||||
@@ -183,6 +249,28 @@ func (qb *userDemoRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value strin
|
||||
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 {
|
||||
@@ -204,6 +292,28 @@ func (qb *userDemoRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value in
|
||||
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 {
|
||||
@@ -225,6 +335,28 @@ func (qb *userDemoRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value ti
|
||||
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 {
|
||||
@@ -246,6 +378,28 @@ func (qb *userDemoRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value ti
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+15
-5
@@ -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)
|
||||
}
|
||||
+26
-30
@@ -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)
|
||||
}
|
||||
Vendored
+10
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
@@ -0,0 +1,89 @@
|
||||
package dashboard_handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/env"
|
||||
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
const (
|
||||
B = 1
|
||||
KB = 1024 * B
|
||||
MB = 1024 * KB
|
||||
GB = 1024 * MB
|
||||
)
|
||||
|
||||
type viewResponse struct {
|
||||
MemTotal string
|
||||
MemUsed string
|
||||
MemUsedPercent float64
|
||||
|
||||
DiskTotal string
|
||||
DiskUsed string
|
||||
DiskUsedPercent float64
|
||||
|
||||
HostOS string
|
||||
HostName string
|
||||
|
||||
CpuName string
|
||||
CpuCores int32
|
||||
CpuUsedPercent float64
|
||||
|
||||
GoPath string
|
||||
GoVersion string
|
||||
Goroutine int
|
||||
ProjectPath string
|
||||
Env string
|
||||
Host string
|
||||
GoOS string
|
||||
GoArch string
|
||||
}
|
||||
|
||||
func (h *handler) View() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
memInfo, _ := mem.VirtualMemory()
|
||||
diskInfo, _ := disk.Usage("/")
|
||||
hostInfo, _ := host.Info()
|
||||
cpuInfo, _ := cpu.Info()
|
||||
cpuPercent, _ := cpu.Percent(time.Second, false)
|
||||
|
||||
obj := new(viewResponse)
|
||||
obj.MemTotal = fmt.Sprintf("%d GB", memInfo.Total/GB)
|
||||
obj.MemUsed = fmt.Sprintf("%d GB", memInfo.Used/GB)
|
||||
obj.MemUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", memInfo.UsedPercent), 64)
|
||||
|
||||
obj.DiskTotal = fmt.Sprintf("%d GB", diskInfo.Total/GB)
|
||||
obj.DiskUsed = fmt.Sprintf("%d GB", diskInfo.Used/GB)
|
||||
obj.DiskUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", diskInfo.UsedPercent), 64)
|
||||
|
||||
obj.HostOS = fmt.Sprintf("%s(%s) %s", hostInfo.Platform, hostInfo.PlatformFamily, hostInfo.PlatformVersion)
|
||||
obj.HostName = hostInfo.Hostname
|
||||
|
||||
obj.CpuName = cpuInfo[0].ModelName
|
||||
obj.CpuCores = cpuInfo[0].Cores
|
||||
obj.CpuUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", cpuPercent[0]), 64)
|
||||
|
||||
obj.GoPath = runtime.GOROOT()
|
||||
obj.GoVersion = runtime.Version()
|
||||
obj.Goroutine = runtime.NumGoroutine()
|
||||
dir, _ := os.Getwd()
|
||||
obj.ProjectPath = strings.Replace(dir, "\\", "/", -1)
|
||||
obj.Host = c.Host()
|
||||
obj.Env = env.Active().Value()
|
||||
obj.GoOS = runtime.GOOS
|
||||
obj.GoArch = runtime.GOARCH
|
||||
|
||||
c.HTML("dashboard", obj)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package dashboard_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() {}
|
||||
@@ -0,0 +1,44 @@
|
||||
package gencode_handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
)
|
||||
|
||||
type gormExecuteRequest struct {
|
||||
Tables string `form:"tables"`
|
||||
}
|
||||
|
||||
func (h *handler) GormExecute() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
|
||||
req := new(gormExecuteRequest)
|
||||
if err := c.ShouldBindPostForm(req); err != nil {
|
||||
c.Payload("参数传递有误")
|
||||
return
|
||||
}
|
||||
|
||||
mysqlConf := configs.Get().MySQL.Read
|
||||
shellPath := fmt.Sprintf("./scripts/gormgen.sh %s %s %s %s %s", mysqlConf.Addr, mysqlConf.User, mysqlConf.Pass, mysqlConf.Name, req.Tables)
|
||||
|
||||
command := exec.Command("/bin/bash", "-c", shellPath) //初始化 Cmd
|
||||
|
||||
// windows 版本
|
||||
//command := exec.Command("cmd", "/C", shellPath)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
command.Stderr = &stderr
|
||||
|
||||
output, err := command.Output()
|
||||
if err != nil {
|
||||
c.Payload(stderr.String())
|
||||
return
|
||||
}
|
||||
|
||||
c.Payload(string(output))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package gencode_handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
)
|
||||
|
||||
func (h *handler) GormView() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
|
||||
type tableInfo struct {
|
||||
Name string `db:"table_name"` // name
|
||||
Comment string `db:"table_comment"` // comment
|
||||
}
|
||||
|
||||
var tableCollect []tableInfo
|
||||
|
||||
mysqlConf := configs.Get().MySQL.Read
|
||||
sqlTables := fmt.Sprintf("SELECT `table_name`,`table_comment` FROM `information_schema`.`tables` WHERE `table_schema`= '%s'", mysqlConf.Name)
|
||||
rows, err := h.db.GetDbR().Raw(sqlTables).Rows()
|
||||
if err != nil {
|
||||
c.HTML("gormgen", tableCollect)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
c.HTML("gencode_gorm", tableCollect)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package gencode_handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
)
|
||||
|
||||
type handlerExecuteRequest struct {
|
||||
Name string `form:"name"`
|
||||
}
|
||||
|
||||
func (h *handler) HandlerExecute() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(handlerExecuteRequest)
|
||||
if err := c.ShouldBindPostForm(req); err != nil {
|
||||
c.Payload("参数传递有误")
|
||||
return
|
||||
}
|
||||
|
||||
shellPath := fmt.Sprintf("./scripts/handlergen.sh %s", req.Name)
|
||||
command := exec.Command("/bin/bash", "-c", shellPath) //初始化 Cmd
|
||||
|
||||
// windows 版本
|
||||
//command := exec.Command("cmd", "/C", shellPath)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
command.Stderr = &stderr
|
||||
|
||||
output, err := command.Output()
|
||||
if err != nil {
|
||||
c.Payload(stderr.String())
|
||||
return
|
||||
}
|
||||
|
||||
c.Payload(string(output))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package gencode_handler
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
)
|
||||
|
||||
func (h *handler) HandlerView() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
c.HTML("gencode_handler", nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package gencode_handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
)
|
||||
|
||||
func (h *handler) InitExecute() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
mysqlConf := configs.Get().MySQL.Read
|
||||
shellPath := fmt.Sprintf("./scripts/init.sh %s %s %s %s", mysqlConf.Addr, mysqlConf.User, mysqlConf.Pass, mysqlConf.Name)
|
||||
|
||||
command := exec.Command("/bin/bash", "-c", shellPath) //初始化 Cmd
|
||||
|
||||
// windows 版本
|
||||
//command := exec.Command("cmd", "/C", shellPath)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
command.Stderr = &stderr
|
||||
|
||||
output, err := command.Output()
|
||||
if err != nil {
|
||||
c.Payload(stderr.String())
|
||||
return
|
||||
}
|
||||
|
||||
c.Payload(string(output))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package gencode_handler
|
||||
|
||||
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
|
||||
func (h *handler) InitView() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
c.HTML("gencode_init", nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package gencode_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()
|
||||
|
||||
InitView() core.HandlerFunc
|
||||
InitExecute() core.HandlerFunc
|
||||
|
||||
HandlerView() core.HandlerFunc
|
||||
HandlerExecute() core.HandlerFunc
|
||||
|
||||
GormView() core.HandlerFunc
|
||||
GormExecute() core.HandlerFunc
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
db db.Repo
|
||||
logger *zap.Logger
|
||||
cache cache.Repo
|
||||
}
|
||||
|
||||
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
|
||||
return &handler{
|
||||
logger: logger,
|
||||
cache: cache,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) i() {}
|
||||
@@ -0,0 +1,9 @@
|
||||
package index_handler
|
||||
|
||||
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
|
||||
func (h *handler) View() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
c.HTML("index", nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package index_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() {}
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/router"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
|
||||
"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/router"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/env"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/logger"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/shutdown"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
## 与 UrlSign 对应的 PHP 加密算法
|
||||
|
||||
```php
|
||||
// 对 params key 进行排序
|
||||
ksort($params);
|
||||
|
||||
// 对 sortParams 进行 Encode
|
||||
$sortParamsEncode = http_build_query($params);
|
||||
|
||||
// 加密字符串规则 path + method + sortParamsEncode + secret
|
||||
$encryptStr = $path . $method . $sortParamsEncode . $secret
|
||||
|
||||
// 对加密字符串进行 md5
|
||||
$md5Str = md5($encryptStr);
|
||||
|
||||
// 对 md5Str 进行 base64 encode
|
||||
$tokenString = base64_encode($md5Str);
|
||||
|
||||
echo $tokenString;
|
||||
```
|
||||
+8
-38
@@ -1,6 +1,7 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
@@ -11,8 +12,13 @@ var _ Token = (*token)(nil)
|
||||
type Token interface {
|
||||
// i 为了避免被其他包实现
|
||||
i()
|
||||
Sign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error)
|
||||
Parse(tokenString string) (*claims, error)
|
||||
|
||||
// JWT 签名方式
|
||||
JwtSign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error)
|
||||
JwtParse(tokenString string) (*claims, error)
|
||||
|
||||
// URL 签名方式,不支持解密
|
||||
UrlSign(path string, method string, params url.Values) (tokenString string, err error)
|
||||
}
|
||||
|
||||
type token struct {
|
||||
@@ -32,39 +38,3 @@ func New(secret string) Token {
|
||||
}
|
||||
|
||||
func (t *token) i() {}
|
||||
|
||||
func (t *token) Sign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error) {
|
||||
// The token content.
|
||||
// iss: (Issuer)签发者
|
||||
// iat: (Issued At)签发时间,用Unix时间戳表示
|
||||
// exp: (Expiration Time)过期时间,用Unix时间戳表示
|
||||
// aud: (Audience)接收该JWT的一方
|
||||
// sub: (Subject)该JWT的主题
|
||||
// nbf: (Not Before)不要早于这个时间
|
||||
// jti: (JWT ID)用于标识JWT的唯一ID
|
||||
claims := claims{
|
||||
userId,
|
||||
userName,
|
||||
jwt.StandardClaims{
|
||||
NotBefore: time.Now().Unix(),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(expireDuration).Unix(),
|
||||
},
|
||||
}
|
||||
tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(t.secret))
|
||||
return
|
||||
}
|
||||
|
||||
func (t *token) Parse(tokenString string) (*claims, error) {
|
||||
tokenClaims, err := jwt.ParseWithClaims(tokenString, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(t.secret), nil
|
||||
})
|
||||
|
||||
if tokenClaims != nil {
|
||||
if claims, ok := tokenClaims.Claims.(*claims); ok && tokenClaims.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
func (t *token) JwtSign(userId int64, userName string, expireDuration time.Duration) (tokenString string, err error) {
|
||||
// The token content.
|
||||
// iss: (Issuer)签发者
|
||||
// iat: (Issued At)签发时间,用Unix时间戳表示
|
||||
// exp: (Expiration Time)过期时间,用Unix时间戳表示
|
||||
// aud: (Audience)接收该JWT的一方
|
||||
// sub: (Subject)该JWT的主题
|
||||
// nbf: (Not Before)不要早于这个时间
|
||||
// jti: (JWT ID)用于标识JWT的唯一ID
|
||||
claims := claims{
|
||||
userId,
|
||||
userName,
|
||||
jwt.StandardClaims{
|
||||
NotBefore: time.Now().Unix(),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(expireDuration).Unix(),
|
||||
},
|
||||
}
|
||||
tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(t.secret))
|
||||
return
|
||||
}
|
||||
|
||||
func (t *token) JwtParse(tokenString string) (*claims, error) {
|
||||
tokenClaims, err := jwt.ParseWithClaims(tokenString, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(t.secret), nil
|
||||
})
|
||||
|
||||
if tokenClaims != nil {
|
||||
if claims, ok := tokenClaims.Claims.(*claims); ok && tokenClaims.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
+25
-7
@@ -1,14 +1,15 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const secret = "i1ydX9RtHyuJTrw7frcu"
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
tokenString, err := New(secret).Sign(123456789, "xinliangnote", 24*time.Hour)
|
||||
func TestJwtSign(t *testing.T) {
|
||||
tokenString, err := New(secret).JwtSign(123456789, "xinliangnote", 24*time.Hour)
|
||||
if err != nil {
|
||||
t.Error("sign error", err)
|
||||
return
|
||||
@@ -16,9 +17,9 @@ func TestSign(t *testing.T) {
|
||||
t.Log(tokenString)
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
func TestJwtParse(t *testing.T) {
|
||||
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMzQ1Njc4OSwidXNlcm5hbWUiOiJ4aW5saWFuZyIsImV4cCI6MTYwOTQ2NzcwNCwiaWF0IjoxNjA5MzgxMzA0LCJpc3MiOiJnby1naW4tYXBpIiwibmJmIjoxNjA5MzgxMzA0fQ.hccv8F713WpKcwiSldBrFLZz_2SZzOTPedPi-8ps7M4"
|
||||
user, err := New(secret).Parse(tokenString)
|
||||
user, err := New(secret).JwtParse(tokenString)
|
||||
if err != nil {
|
||||
t.Error("parse error", err)
|
||||
return
|
||||
@@ -26,11 +27,28 @@ func TestParse(t *testing.T) {
|
||||
t.Log(user)
|
||||
}
|
||||
|
||||
func BenchmarkSignAndParse(b *testing.B) {
|
||||
func TestUrlSign(t *testing.T) {
|
||||
|
||||
urlPath := "/echo"
|
||||
method := "post"
|
||||
params := url.Values{}
|
||||
params.Add("a", "a1")
|
||||
params.Add("d", "d1")
|
||||
params.Add("c", "c1")
|
||||
|
||||
tokenString, err := New(secret).UrlSign(urlPath, method, params)
|
||||
if err != nil {
|
||||
t.Error("sign error", err)
|
||||
return
|
||||
}
|
||||
t.Log(tokenString)
|
||||
}
|
||||
|
||||
func BenchmarkJwtSignAndParse(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
token := New(secret)
|
||||
for i := 0; i < b.N; i++ {
|
||||
tokenString, _ := token.Sign(123456789, "xinliangnote", 24*time.Hour)
|
||||
token.Parse(tokenString)
|
||||
tokenString, _ := token.JwtSign(123456789, "xinliangnote", 24*time.Hour)
|
||||
token.JwtParse(tokenString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UrlSign
|
||||
// path 请求的路径 (不附带 querystring)
|
||||
func (t *token) UrlSign(path string, method string, params url.Values) (tokenString string, err error) {
|
||||
// 合法的 Methods
|
||||
methods := map[string]bool{
|
||||
"get": true,
|
||||
"post": true,
|
||||
"put": true,
|
||||
"path": true,
|
||||
"delete": true,
|
||||
"head": true,
|
||||
"options": true,
|
||||
}
|
||||
|
||||
methodName := strings.ToLower(method)
|
||||
if !methods[methodName] {
|
||||
err = errors.New("method param error")
|
||||
return
|
||||
}
|
||||
|
||||
// Encode() 方法中自带 sorted by key
|
||||
sortParamsEncode := params.Encode()
|
||||
|
||||
// 加密字符串规则 path + method + sortParamsEncode + secret
|
||||
encryptStr := fmt.Sprintf("%s%s%s%s", path, methodName, sortParamsEncode, t.secret)
|
||||
|
||||
// 对加密字符串进行 md5
|
||||
s := md5.New()
|
||||
s.Write([]byte(encryptStr))
|
||||
md5Str := hex.EncodeToString(s.Sum(nil))
|
||||
|
||||
// 对 md5Str 进行 base64 encode
|
||||
tokenString = base64.StdEncoding.EncodeToString([]byte(md5Str))
|
||||
|
||||
return
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user