This commit is contained in:
新亮
2021-04-18 19:54:31 +08:00
parent 3e1ef9c658
commit 1f85d8df61
64 changed files with 1356 additions and 2758 deletions

View File

@@ -0,0 +1,24 @@
/*
jQuery twitter bootstrap wizard plugin
Examples and documentation at: http://github.com/VinceG/twitter-bootstrap-wizard
version 1.4.2
Requires jQuery v1.3.2 or later
Supports Bootstrap 2.2.x, 2.3.x, 3.0
Dual licensed under the MIT and GPL licenses:
http://www.opensource.org/licenses/mit-license.php
http://www.gnu.org/licenses/gpl.html
Authors: Vadim Vincent Gabriel (http://vadimg.com), Jason Gill (www.gilluminate.com)
*/
(function(c){var n=function(d,k){d=c(d);var a=this,h=[],b=c.extend({},c.fn.bootstrapWizard.defaults,k),f=null,e=null;this.rebindClick=function(b,a){b.unbind("click",a).bind("click",a)};this.fixNavigationButtons=function(){f.length||(e.find("a:first").tab("show"),f=e.find('li:has([data-toggle="tab"]):first'));c(b.previousSelector,d).toggleClass("disabled",a.firstIndex()>=a.currentIndex());c(b.nextSelector,d).toggleClass("disabled",a.currentIndex()>=a.navigationLength());c(b.nextSelector,d).toggleClass("d-none",
a.currentIndex()>=a.navigationLength()&&0<c(b.finishSelector,d).length);c(b.lastSelector,d).toggleClass("d-none",a.currentIndex()>=a.navigationLength()&&0<c(b.finishSelector,d).length);c(b.finishSelector,d).toggleClass("d-none",a.currentIndex()<a.navigationLength());c(b.backSelector,d).toggleClass("disabled",0==h.length);c(b.backSelector,d).toggleClass("d-none",a.currentIndex()>=a.navigationLength()&&0<c(b.finishSelector,d).length);a.rebindClick(c(b.nextSelector,d),a.next);a.rebindClick(c(b.previousSelector,
d),a.previous);a.rebindClick(c(b.lastSelector,d),a.last);a.rebindClick(c(b.firstSelector,d),a.first);a.rebindClick(c(b.finishSelector,d),a.finish);a.rebindClick(c(b.backSelector,d),a.back);if(b.onTabShow&&"function"===typeof b.onTabShow&&!1===b.onTabShow(f,e,a.currentIndex()))return!1};this.next=function(g){if(d.hasClass("last")||b.onNext&&"function"===typeof b.onNext&&!1===b.onNext(f,e,a.nextIndex()))return!1;g=a.currentIndex();var c=a.nextIndex();c>a.navigationLength()||(h.push(g),e.find('li:has([data-toggle="tab"])'+
(b.withVisible?":visible":"")+":eq("+c+") a").tab("show"))};this.previous=function(g){if(d.hasClass("first")||b.onPrevious&&"function"===typeof b.onPrevious&&!1===b.onPrevious(f,e,a.previousIndex()))return!1;g=a.currentIndex();var c=a.previousIndex();0>c||(h.push(g),e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")+":eq("+c+") a").tab("show"))};this.first=function(g){if(b.onFirst&&"function"===typeof b.onFirst&&!1===b.onFirst(f,e,a.firstIndex())||d.hasClass("disabled"))return!1;h.push(a.currentIndex());
e.find('li:has([data-toggle="tab"]):eq(0) a').tab("show")};this.last=function(g){if(b.onLast&&"function"===typeof b.onLast&&!1===b.onLast(f,e,a.lastIndex())||d.hasClass("disabled"))return!1;h.push(a.currentIndex());e.find('li:has([data-toggle="tab"]):eq('+a.navigationLength()+") a").tab("show")};this.finish=function(g){if(b.onFinish&&"function"===typeof b.onFinish)b.onFinish(f,e,a.lastIndex())};this.back=function(){if(0==h.length)return null;var a=h.pop();if(b.onBack&&"function"===typeof b.onBack&&
!1===b.onBack(f,e,a))return h.push(a),!1;d.find('li:has([data-toggle="tab"]):eq('+a+") a").tab("show")};this.currentIndex=function(){return e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")).index(f)};this.firstIndex=function(){return 0};this.lastIndex=function(){return a.navigationLength()};this.getIndex=function(a){return e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")).index(a)};this.nextIndex=function(){var a=this.currentIndex(),c;do a++,c=e.find('li:has([data-toggle="tab"])'+
(b.withVisible?":visible":"")+":eq("+a+")");while(c&&c.hasClass("disabled"));return a};this.previousIndex=function(){var a=this.currentIndex(),c;do a--,c=e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")+":eq("+a+")");while(c&&c.hasClass("disabled"));return a};this.navigationLength=function(){return e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")).length-1};this.activeTab=function(){return f};this.nextTab=function(){return e.find('li:has([data-toggle="tab"]):eq('+
(a.currentIndex()+1)+")").length?e.find('li:has([data-toggle="tab"]):eq('+(a.currentIndex()+1)+")"):null};this.previousTab=function(){return 0>=a.currentIndex()?null:e.find('li:has([data-toggle="tab"]):eq('+parseInt(a.currentIndex()-1)+")")};this.show=function(b){b=isNaN(b)?d.find('li:has([data-toggle="tab"]) a[href="#'+b+'"]'):d.find('li:has([data-toggle="tab"]):eq('+b+") a");0<b.length&&(h.push(a.currentIndex()),b.tab("show"))};this.disable=function(a){e.find('li:has([data-toggle="tab"]):eq('+a+
")").addClass("disabled")};this.enable=function(a){e.find('li:has([data-toggle="tab"]):eq('+a+")").removeClass("disabled")};this.hide=function(a){e.find('li:has([data-toggle="tab"]):eq('+a+")").hide()};this.display=function(a){e.find('li:has([data-toggle="tab"]):eq('+a+")").show()};this.remove=function(a){var b="undefined"!=typeof a[1]?a[1]:!1;a=e.find('li:has([data-toggle="tab"]):eq('+a[0]+")");b&&(b=a.find("a").attr("href"),c(b).remove());a.remove()};var l=function(d){var g=e.find('li:has([data-toggle="tab"])');
d=g.index(c(d.currentTarget).parent('li:has([data-toggle="tab"])'));g=c(g[d]);if(b.onTabClick&&"function"===typeof b.onTabClick&&!1===b.onTabClick(f,e,a.currentIndex(),d,g))return!1},m=function(d){d=c(d.target).parent();var g=e.find('li:has([data-toggle="tab"])').index(d);if(d.hasClass("disabled")||b.onTabChange&&"function"===typeof b.onTabChange&&!1===b.onTabChange(f,e,a.currentIndex(),g))return!1;f=d;a.fixNavigationButtons()};this.resetWizard=function(){c('a[data-toggle="tab"]',e).off("click",l);
c('a[data-toggle="tab"]',e).off("show show.bs.tab",m);e=d.find("ul:first",d);f=e.find('li:has([data-toggle="tab"]).active',d);c('a[data-toggle="tab"]',e).on("click",l);c('a[data-toggle="tab"]',e).on("show show.bs.tab",m);a.fixNavigationButtons()};e=d.find("ul:first",d);f=e.find('li:has([data-toggle="tab"]).active',d);e.hasClass(b.tabClass)||e.addClass(b.tabClass);if(b.onInit&&"function"===typeof b.onInit)b.onInit(f,e,0);if(b.onShow&&"function"===typeof b.onShow)b.onShow(f,e,a.nextIndex());c('a[data-toggle="tab"]',
e).on("click",l);c('a[data-toggle="tab"]',e).on("show show.bs.tab",m)};c.fn.bootstrapWizard=function(d){if("string"==typeof d){var k=Array.prototype.slice.call(arguments,1);1===k.length&&k.toString();return this.data("bootstrapWizard")[d](k)}return this.each(function(a){a=c(this);if(!a.data("bootstrapWizard")){var h=new n(a,d);a.data("bootstrapWizard",h);h.fixNavigationButtons()}})};c.fn.bootstrapWizard.defaults={withVisible:!0,tabClass:"nav nav-pills",nextSelector:".wizard li.next",previousSelector:".wizard li.previous",
firstSelector:".wizard li.first",lastSelector:".wizard li.last",finishSelector:".wizard li.finish",backSelector:".wizard li.back",onShow:null,onInit:null,onNext:null,onPrevious:null,onLast:null,onFirst:null,onFinish:null,onBack:null,onTabChange:null,onTabClick:null,onTabShow:null}})(jQuery);

View File

@@ -0,0 +1,33 @@
<!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">
{{range .}}
<p>
<code>{{.Code}}</code> {{.Message}}
<hr>
</p>
{{end}}
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,169 @@
<!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/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<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">
<form class="site-form">
<div class="form-group">
<label>邮箱服务器:</label>
<input type="text" class="form-control" id="host" value="{{ .Mail.Host }}">
</div>
<div class="form-group">
<label>端口:</label>
<input type="text" class="form-control" id="port" value="{{ .Mail.Port }}">
</div>
<div class="form-group">
<label>发件人邮箱</label>
<input type="text" class="form-control" id="user" value="{{ .Mail.User }}">
</div>
<div class="form-group">
<label>发件人密码</label> <small>(发件人邮箱密码或授权码,根据邮箱服务器而定)</small>
<input type="password" class="form-control" id="pass" value="{{ .Mail.Pass }}">
</div>
<div class="form-group">
<label>收件人</label> <small>(添加多个收件人邮箱,用英文,分割)</small>
<input type="text" class="form-control" id="to" value="{{ .Mail.To }}"
placeholder="请输入收件人邮箱,多个用,分割">
</div>
<div class="form-group">
<small>为了验证邮箱配置的准确性,点击保存后会给收件人发送邮件以确保配置可用。</small>
</div>
<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>
</form>
</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" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#btnOk').on('click', function () {
const host = $("#host").val();
if (host === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入邮箱服务器。',
});
return false;
}
const port = $("#port").val();
if (port === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入端口。',
});
return false;
}
const user = $("#user").val();
if (user === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入发件人邮箱。',
});
return false;
}
const pass = $("#pass").val();
if (pass === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入发件人密码。',
});
return false;
}
const to = $("#to").val();
if (to === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入收件人邮箱。',
});
return false;
}
const postData = {
host: host,
port: port,
user: user,
pass: pass,
to: to,
};
AjaxForm(
"PATCH",
"/api/config/email",
postData,
function () {
$(this).hide();
$("#btnLoading").show();
},
function () {
$("#btnLoading").hide();
$("#btnOk").show();
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '配置修改完成',
buttons: {
okay: {
text: '关闭',
action: function () {
location.reload();
}
}
}
});
},
function (response) {
$("#btnLoading").hide();
$("#btnOk").show();
AjaxError(response);
}
);
});
})
</script>
</body>
</html>

View File

@@ -1,45 +0,0 @@
<!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>

View File

@@ -15,8 +15,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;">

View File

@@ -15,8 +15,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 名称">

View File

@@ -1,94 +0,0 @@
<!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/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<link href="bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
<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><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>authorized</code> </span></p>
<p><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>authorized_api</code> </span></p>
<p><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>admin</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" src="bootstrap/js/jquery-confirm/jquery-confirm.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();
if (getQueryString("init")) {
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '初始化完成。',
buttons: {
okay: {
text: '系统首页',
action: function () {
location.href = "/";
}
}
}
});
}
})
});
function getQueryString(name) {
let reg = new RegExp('(?:(?:&|\\?)' + name + '=([^&]*))|(?:/' + name + '/([^/]*))', 'i');
let r = window.location.href.match(reg);
if (r != null)
return decodeURI(r[1] || r[2]);
return null;
}
})
</script>
</body>
</html>

View File

@@ -34,12 +34,17 @@
<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-settings-box"></i> <span>配置信息</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/config/email">告警邮箱</a> </li>
<li> <a class="multitabs" href="/config/code">错误码</a> </li>
</ul>
</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>

View File

@@ -0,0 +1,227 @@
<!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/materialdesignicons.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 text-center">
<h4 class="card-title">配置 · Redis</h4>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">地址</span>
</div>
<input type="text" class="form-control" id="redis_addr" value="{{ .Redis.Addr }}"
placeholder="请输入地址例如127.0.0.1:6379">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">密码</span>
</div>
<input type="password" class="form-control" id="redis_pass" value="{{ .Redis.Pass }}"
placeholder="请输入密码">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">DB </span>
</div>
<input type="text" class="form-control" id="redis_db" value="{{ .Redis.Db }}"
placeholder="请输入 DB ,序号从 0 开始,默认是 0">
</div>
<h4 class="card-title">配置 · MySQL</h4>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">地址</span>
</div>
<input type="text" class="form-control" id="mysql_addr" value="{{ .MySQL.Write.Addr }}"
placeholder="请输入服务器地址例如127.0.0.1:3306">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">用户</span>
</div>
<input type="text" class="form-control" id="mysql_user" value="{{ .MySQL.Write.User }}"
placeholder="请输入用户名">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">密码</span>
</div>
<input type="password" class="form-control" id="mysql_pass" value="{{ .MySQL.Write.Pass }}"
placeholder="请输入密码">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">DB </span>
</div>
<input type="text" class="form-control" id="mysql_name" value="{{ .MySQL.Write.Name }}"
placeholder="请输入数据库名">
</div>
<div class="input-group mb-3">
<small><i class="mdi mdi-checkbox-marked-circle"></i> 初始化 MySQL 数据表:<code>authorized</code>
<code>authorized_api</code><code>admin</code>
</small>
</div>
<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>
</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" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#btnOk").click(function () {
const redis_addr = $("#redis_addr").val();
if (redis_addr === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 Redis 服务器地址。',
});
return false;
}
const redis_db = $("#redis_db").val();
if (redis_db === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 Redis DB。',
});
return false;
}
const mysql_addr = $("#mysql_addr").val();
if (mysql_addr === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 MySQL 服务器地址。',
});
return false;
}
const mysql_user = $("#mysql_user").val();
if (mysql_user === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 MySQL 用户名。',
});
return false;
}
const mysql_pass = $("#mysql_pass").val();
if (mysql_pass === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 MySQL 密码。',
});
return false;
}
const mysql_name = $("#mysql_name").val();
if (mysql_name === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 MySQL 数据库名。',
});
return false;
}
const postData = {
redis_addr: redis_addr,
redis_pass: $("#redis_pass").val(),
redis_db: redis_db,
mysql_addr: mysql_addr,
mysql_user: mysql_user,
mysql_pass: mysql_pass,
mysql_name: mysql_name,
};
AjaxForm(
"POST",
"/install/execute",
postData,
function () {
$("#resultDiv").text("");
$(this).hide();
$("#btnLoading").show();
},
function (data) {
$("#resultDiv").text(data);
$("#btnLoading").hide();
$("#btnOk").show();
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '服务初始化成功,<strong style="color: red">请重新启动服务!</strong>',
});
},
function (response) {
$("#btnLoading").hide();
$("#btnOk").show();
AjaxError(response);
}
);
})
});
</script>
</body>
</html>

View File

@@ -1,11 +0,0 @@
## 执行命令
在根目录下执行脚本:`./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
```

View File

@@ -1,113 +0,0 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"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())
}
}()
// 开启事务
tx := db.GetDb().Begin()
// 创建 user_demo 表
err = tx.Exec(mysql.CreateUserDemoTableSql()).Error
if err != nil {
tx.Rollback()
log.Fatal("create user_demo table err: ", err.Error())
}
fmt.Println("create user_demo table success")
// 创建 authorized 表
err = tx.Exec(mysql.CreateAuthorizedTableSql()).Error
if err != nil {
tx.Rollback()
log.Fatal("create authorized table err: ", err.Error())
}
fmt.Println("create authorized table success")
err = tx.Exec(mysql.CreateAuthorizedTableDataSql()).Error
if err != nil {
tx.Rollback()
log.Fatal("create authorized table data err: ", err.Error())
}
fmt.Println("create authorized table data success")
// 创建 authorized_api 表
err = tx.Exec(mysql.CreateAuthorizedAPITableSql()).Error
if err != nil {
tx.Rollback()
log.Fatal("create authorized_api table err: ", err.Error())
}
fmt.Println("create authorized_api table success")
err = tx.Exec(mysql.CreateAuthorizedAPITableDataSql()).Error
if err != nil {
tx.Rollback()
log.Fatal("create authorized_api table data err: ", err.Error())
}
fmt.Println("create authorized_api table data success")
// 创建 admin 表
err = tx.Exec(mysql.CreateAdminTableSql()).Error
if err != nil {
tx.Rollback()
log.Fatal("create admin table err: ", err.Error())
}
fmt.Println("create admin table success")
err = tx.Exec(mysql.CreateAdminTableDataSql()).Error
if err != nil {
tx.Rollback()
log.Fatal("create admin table data err: ", err.Error())
}
fmt.Println("create admin table data success")
// 生成已完成 init 的标识,生成 init_db.lock
f, err := os.Create("cmd/init/db/init_db.lock")
if err != nil {
log.Fatal("create init_db.lock err: ", err.Error())
}
defer f.Close()
// 完成事务
tx.Commit()
}

View File

@@ -1,63 +0,0 @@
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()
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
@@ -77,6 +78,13 @@ func init() {
if err := viper.Unmarshal(config); err != nil {
panic(err)
}
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
if err := viper.Unmarshal(config); err != nil {
panic(err)
}
})
}
func Get() Config {
@@ -95,6 +103,6 @@ func ProjectLogFile() string {
return fmt.Sprintf("./logs/%s-access.log", ProjectName())
}
func InitDBLockFile() string {
return "cmd/init/db/init_db.lock"
func ProjectInstallFile() string {
return "INSTALL.lock"
}

View File

@@ -1,3 +1 @@
[db]
host = '127.0.0.1'
port = 3306

View File

@@ -1,43 +1,46 @@
[mysql]
[mysql.read] # 从库信息,可读
addr = '127.0.0.1:3306' # MySQL 地址:端口
user = 'root' # 用户名
pass = 'root' # 密码
name = 'go_gin_api' # 数据库名称
[mysql.write] # 主库信息,可读写
addr = '127.0.0.1:3306' # MySQL 地址:端口
user = 'root' # 用户名
pass = 'root' # 密码
name = 'go_gin_api' # 数据库名称
[mysql.base] # 基础配置
maxOpenConn = 10 # 最大打开的连接数
maxIdleConn = 60 # 闲置的连接数
connMaxLifeTime = 60 # 最大连接超时(单位:分)
[redis]
addr = '127.0.0.1:6379' # Redis 地址:端口
pass = '' # 密码
db = 0 # 序号从 0 开始默认是0可以不用设置
maxRetries = 3 # 命令执行失败时,最多重试多少次,默认为 0 即不重试
poolSize = 10 # 连接池最大连接数,默认为 CPU 数 * 10
minIdleConns = 5 # 最小空闲连接数
[mail]
host = 'smtp.163.com' # 邮箱服务器,比如 smtp.163.com
port = 465 # 端口
user = "" # 发件人邮箱
pass = "" # 发件人邮箱密码或授权码,根据邮箱服务器而定
to = "" # 收件人邮箱,多个可以逗号(,)分割
[jwt]
secret = 'i1ydX9RtHyuJTrw7frcu' # JWT secret
expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时)
[urlToken]
secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)
[hashids]
secret = '6ab6122836cfef95f8db' # hashids secret
length = 12 # hashids length
length = 12
secret = "6ab6122836cfef95f8db"
[jwt]
expireduration = 24
secret = "i1ydX9RtHyuJTrw7frcu"
[mail]
host = "smtp.163.com"
pass = ""
port = 465
to = ""
user = ""
[mysql]
[mysql.base]
connmaxlifetime = 60
maxidleconn = 60
maxopenconn = 10
[mysql.read]
addr = "127.0.0.1:3306"
name = "go_gin_api"
pass = "root"
user = "root"
[mysql.write]
addr = "127.0.0.1:3306"
name = "go_gin_api"
pass = "root"
user = "root"
[redis]
addr = "127.0.0.1:6379"
db = "0"
maxretries = 3
minidleconns = 5
pass = ""
poolsize = 10
[urltoken]
expireduration = 10
secret = "i1ydX9RtHyuJTrw7frcu"

View File

@@ -1,4 +1,2 @@
[db]
host = '127.0.0.1'
port = 3306

View File

@@ -1,4 +1,2 @@
[db]
host = '127.0.0.1'
port = 3306

View File

@@ -206,7 +206,7 @@ var doc = `{
},
"/api/admin/modify_password": {
"patch": {
"description": "修改个人信息",
"description": "修改密码",
"consumes": [
"multipart/form-data"
],
@@ -216,19 +216,19 @@ var doc = `{
"tags": [
"API.admin"
],
"summary": "修改个人信息",
"summary": "修改密码",
"parameters": [
{
"type": "string",
"description": "昵称",
"name": "nickname",
"description": "旧密码",
"name": "old_password",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "手机号",
"name": "mobile",
"description": "新密码",
"name": "new_password",
"in": "formData",
"required": true
}
@@ -237,7 +237,7 @@ var doc = `{
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/admin_handler.modifyPersonalInfoResponse"
"$ref": "#/definitions/admin_handler.modifyPasswordResponse"
}
},
"400": {
@@ -695,6 +695,72 @@ var doc = `{
}
}
},
"/api/config/email": {
"patch": {
"description": "修改邮件配置",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"API.config"
],
"summary": "修改邮件配置",
"parameters": [
{
"type": "string",
"description": "邮箱服务器",
"name": "host",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "端口",
"name": "port",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "发件人邮箱",
"name": "user",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "发件人密码",
"name": "pass",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "收件人邮箱地址,多个用,分割",
"name": "to",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/config_handler.emailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/tool/hashids/decode/{id}": {
"get": {
"description": "HashIds 解密",
@@ -770,300 +836,6 @@ var doc = `{
}
}
}
},
"/auth/get": {
"post": {
"description": "获取授权信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "获取授权信息",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/demo_handler.authResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/demo/trace": {
"get": {
"description": "Trace 示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "Trace 示例",
"parameters": [
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"job": {
"description": "工作",
"type": "string"
},
"name": {
"description": "用户名",
"type": "string"
}
}
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/create": {
"post": {
"description": "创建用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "创建用户",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.createRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.createResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "删除用户",
"parameters": [
{
"type": "integer",
"description": "用户ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.deleteResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/info/{username}": {
"get": {
"description": "用户详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "用户详情",
"parameters": [
{
"type": "string",
"description": "用户名",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.detailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/update": {
"put": {
"description": "编辑用户 - 通过用户主键ID更新用户昵称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "编辑用户 - 通过用户主键ID更新用户昵称",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
}
},
"definitions": {
@@ -1390,16 +1162,12 @@ var doc = `{
}
}
},
"demo_handler.authResponse": {
"config_handler.emailResponse": {
"type": "object",
"properties": {
"authorization": {
"description": "签名",
"email": {
"description": "邮箱地址",
"type": "string"
},
"expire_time": {
"description": "过期时间",
"type": "integer"
}
}
},
@@ -1420,84 +1188,6 @@ var doc = `{
"type": "string"
}
}
},
"user_handler.createRequest": {
"type": "object",
"properties": {
"mobile": {
"description": "手机号",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.createResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"user_handler.deleteResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
},
"user_handler.detailResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"mobile": {
"description": "手机号(脱敏)",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDRequest": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"nick_name": {
"description": "昵称",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
}
}
}`

View File

@@ -189,7 +189,7 @@
},
"/api/admin/modify_password": {
"patch": {
"description": "修改个人信息",
"description": "修改密码",
"consumes": [
"multipart/form-data"
],
@@ -199,19 +199,19 @@
"tags": [
"API.admin"
],
"summary": "修改个人信息",
"summary": "修改密码",
"parameters": [
{
"type": "string",
"description": "昵称",
"name": "nickname",
"description": "旧密码",
"name": "old_password",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "手机号",
"name": "mobile",
"description": "新密码",
"name": "new_password",
"in": "formData",
"required": true
}
@@ -220,7 +220,7 @@
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/admin_handler.modifyPersonalInfoResponse"
"$ref": "#/definitions/admin_handler.modifyPasswordResponse"
}
},
"400": {
@@ -678,6 +678,72 @@
}
}
},
"/api/config/email": {
"patch": {
"description": "修改邮件配置",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"API.config"
],
"summary": "修改邮件配置",
"parameters": [
{
"type": "string",
"description": "邮箱服务器",
"name": "host",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "端口",
"name": "port",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "发件人邮箱",
"name": "user",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "发件人密码",
"name": "pass",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "收件人邮箱地址,多个用,分割",
"name": "to",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/config_handler.emailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/tool/hashids/decode/{id}": {
"get": {
"description": "HashIds 解密",
@@ -753,300 +819,6 @@
}
}
}
},
"/auth/get": {
"post": {
"description": "获取授权信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "获取授权信息",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/demo_handler.authResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/demo/trace": {
"get": {
"description": "Trace 示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Demo"
],
"summary": "Trace 示例",
"parameters": [
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"job": {
"description": "工作",
"type": "string"
},
"name": {
"description": "用户名",
"type": "string"
}
}
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/create": {
"post": {
"description": "创建用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "创建用户",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.createRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.createResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "删除用户",
"parameters": [
{
"type": "integer",
"description": "用户ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.deleteResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/info/{username}": {
"get": {
"description": "用户详情",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "用户详情",
"parameters": [
{
"type": "string",
"description": "用户名",
"name": "username",
"in": "path",
"required": true
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.detailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/user/update": {
"put": {
"description": "编辑用户 - 通过用户主键ID更新用户昵称",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "编辑用户 - 通过用户主键ID更新用户昵称",
"parameters": [
{
"description": "请求信息",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDRequest"
}
},
{
"type": "string",
"description": "签名",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.updateNickNameByIDResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
}
},
"definitions": {
@@ -1373,16 +1145,12 @@
}
}
},
"demo_handler.authResponse": {
"config_handler.emailResponse": {
"type": "object",
"properties": {
"authorization": {
"description": "签名",
"email": {
"description": "邮箱地址",
"type": "string"
},
"expire_time": {
"description": "过期时间",
"type": "integer"
}
}
},
@@ -1403,84 +1171,6 @@
"type": "string"
}
}
},
"user_handler.createRequest": {
"type": "object",
"properties": {
"mobile": {
"description": "手机号",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.createResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"user_handler.deleteResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
},
"user_handler.detailResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"mobile": {
"description": "手机号(脱敏)",
"type": "string"
},
"nick_name": {
"description": "昵称",
"type": "string"
},
"user_name": {
"description": "用户名",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDRequest": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
},
"nick_name": {
"description": "昵称",
"type": "string"
}
}
},
"user_handler.updateNickNameByIDResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
}
}
}

View File

@@ -221,14 +221,11 @@ definitions:
description: 描述信息
type: string
type: object
demo_handler.authResponse:
config_handler.emailResponse:
properties:
authorization:
description: 签名
email:
description: 邮箱地址
type: string
expire_time:
description: 过期时间
type: integer
type: object
tool_handler.hashIdsDecodeResponse:
properties:
@@ -242,60 +239,6 @@ definitions:
description: 加密后的值
type: string
type: object
user_handler.createRequest:
properties:
mobile:
description: 手机号
type: string
nick_name:
description: 昵称
type: string
user_name:
description: 用户名
type: string
type: object
user_handler.createResponse:
properties:
id:
description: 主键ID
type: integer
type: object
user_handler.deleteResponse:
properties:
id:
description: 用户主键ID
type: integer
type: object
user_handler.detailResponse:
properties:
id:
description: 用户主键ID
type: integer
mobile:
description: 手机号(脱敏)
type: string
nick_name:
description: 昵称
type: string
user_name:
description: 用户名
type: string
type: object
user_handler.updateNickNameByIDRequest:
properties:
id:
description: 用户主键ID
type: integer
nick_name:
description: 昵称
type: string
type: object
user_handler.updateNickNameByIDResponse:
properties:
id:
description: 用户主键ID
type: integer
type: object
host: 127.0.0.1:9999
info:
contact: {}
@@ -451,16 +394,16 @@ paths:
patch:
consumes:
- multipart/form-data
description: 修改个人信息
description: 修改密码
parameters:
- description: 昵称
- description: 旧密码
in: formData
name: nickname
name: old_password
required: true
type: string
- description: 手机号
- description: 新密码
in: formData
name: mobile
name: new_password
required: true
type: string
produces:
@@ -469,12 +412,12 @@ paths:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.modifyPersonalInfoResponse'
$ref: '#/definitions/admin_handler.modifyPasswordResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 修改个人信息
summary: 修改密码
tags:
- API.admin
/api/admin/reset_password/{id}:
@@ -748,6 +691,51 @@ paths:
summary: 删除调用方接口地址
tags:
- API.authorized
/api/config/email:
patch:
consumes:
- multipart/form-data
description: 修改邮件配置
parameters:
- description: 邮箱服务器
in: formData
name: host
required: true
type: string
- description: 端口
in: formData
name: port
required: true
type: string
- description: 发件人邮箱
in: formData
name: user
required: true
type: string
- description: 发件人密码
in: formData
name: pass
required: true
type: string
- description: 收件人邮箱地址,多个用,分割
in: formData
name: to
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/config_handler.emailResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 修改邮件配置
tags:
- API.config
/api/tool/hashids/decode/{id}:
get:
consumes:
@@ -798,199 +786,4 @@ paths:
summary: HashIds 加密
tags:
- API.tool
/auth/get:
post:
consumes:
- application/json
description: 获取授权信息
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/demo_handler.authResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 获取授权信息
tags:
- Demo
/demo/trace:
get:
consumes:
- application/json
description: Trace 示例
parameters:
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
properties:
job:
description: 工作
type: string
name:
description: 用户名
type: string
type: object
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: Trace 示例
tags:
- Demo
/user/create:
post:
consumes:
- application/json
description: 创建用户
parameters:
- description: 请求信息
in: body
name: Request
required: true
schema:
$ref: '#/definitions/user_handler.createRequest'
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.createResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 创建用户
tags:
- User
/user/delete/{id}:
patch:
consumes:
- application/json
description: 删除用户
parameters:
- description: 用户ID
in: path
name: id
required: true
type: integer
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.deleteResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 删除用户
tags:
- User
/user/info/{username}:
get:
consumes:
- application/json
description: 用户详情
parameters:
- description: 用户名
in: path
name: username
required: true
type: string
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.detailResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 用户详情
tags:
- User
/user/update:
put:
consumes:
- application/json
description: 编辑用户 - 通过用户主键ID更新用户昵称
parameters:
- description: 请求信息
in: body
name: Request
required: true
schema:
$ref: '#/definitions/user_handler.updateNickNameByIDRequest'
- description: 签名
in: header
name: Authorization
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/user_handler.updateNickNameByIDResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 编辑用户 - 通过用户主键ID更新用户昵称
tags:
- User
swagger: "2.0"

1
go.mod
View File

@@ -8,6 +8,7 @@ require (
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/fsnotify/fsnotify v1.4.9
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

View File

@@ -41,10 +41,17 @@ const (
AdminDeleteError = 20303
AdminUpdateError = 20304
AdminResetPasswordError = 20305
AdminLoginError = 20307
AdminLogOutError = 20308
AdminModifyPasswordError = 20309
AdminModifyPersonalInfoError = 20310
AdminLoginError = 20306
AdminLogOutError = 20307
AdminModifyPasswordError = 20308
AdminModifyPersonalInfoError = 20309
// 配置
ConfigEmailError = 20401
ConfigSaveError = 20402
ConfigRedisConnectError = 20403
ConfigMySQLConnectError = 20404
ConfigMySQLInstallError = 20405
)
var codeText = map[int]string{
@@ -81,6 +88,12 @@ var codeText = map[int]string{
AdminLogOutError: "退出失败",
AdminModifyPasswordError: "修改密码失败",
AdminModifyPersonalInfoError: "修改个人信息失败",
ConfigEmailError: "修改邮箱配置失败",
ConfigSaveError: "写入配置文件失败",
ConfigRedisConnectError: "Redis 连接失败",
ConfigMySQLConnectError: "MySQL 连接失败",
ConfigMySQLInstallError: "MySQL 初始化数据失败",
}
func Text(code int) string {

View File

@@ -0,0 +1,98 @@
package config_handler
import (
"fmt"
"net/http"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/mail"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
type emailRequest struct {
Host string `form:"host"` // 邮箱服务器
Port string `form:"port"` // 端口
User string `form:"user"` // 发件人邮箱
Pass string `form:"pass"` // 发件人密码
To string `form:"to"` // 收件人邮箱地址,多个用,分割
}
type emailResponse struct {
Email string `json:"email"` // 邮箱地址
}
// Email 修改邮件配置
// @Summary 修改邮件配置
// @Description 修改邮件配置
// @Tags API.config
// @Accept multipart/form-data
// @Produce json
// @Param host formData string true "邮箱服务器"
// @Param port formData string true "端口"
// @Param user formData string true "发件人邮箱"
// @Param pass formData string true "发件人密码"
// @Param to formData string true "收件人邮箱地址,多个用,分割"
// @Success 200 {object} emailResponse
// @Failure 400 {object} code.Failure
// @Router /api/config/email [patch]
func (h *handler) Email() core.HandlerFunc {
return func(c core.Context) {
req := new(emailRequest)
res := new(emailResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
options := &mail.Options{
MailHost: req.Host,
MailPort: cast.ToInt(req.Port),
MailUser: req.User,
MailPass: req.Pass,
MailTo: req.To,
Subject: fmt.Sprintf("%s[%s] 邮箱告警人调整通知。", configs.ProjectName(), env.Active().Value()),
Body: fmt.Sprintf("%s[%s] 已添加您为系统告警通知人。", configs.ProjectName(), env.Active().Value()),
}
if err := mail.Send(options); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigEmailError,
"Mail Send error: "+err.Error()).WithErr(err),
)
return
}
viper.SetConfigName(env.Active().Value() + "_configs")
viper.SetConfigType("toml")
viper.AddConfigPath("./configs")
viper.Set("mail.host", req.Host)
viper.Set("mail.port", cast.ToInt(req.Port))
viper.Set("mail.user", req.User)
viper.Set("mail.pass", req.Pass)
viper.Set("mail.to", req.To)
err := viper.WriteConfig()
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigEmailError,
code.Text(code.ConfigEmailError)).WithErr(err),
)
return
}
res.Email = req.To
c.Payload(res)
}
}

View File

@@ -0,0 +1,34 @@
package config_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()
// Email 修改邮件配置
// @Tags API.config
// @Router /api/config/email [patch]
Email() 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() {}

View File

@@ -1,47 +0,0 @@
package demo_handler
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/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/token"
)
type authResponse struct {
Authorization string `json:"authorization"` // 签名
ExpireTime int64 `json:"expire_time"` // 过期时间
}
// 获取授权信息
// @Summary 获取授权信息
// @Description 获取授权信息
// @Tags Demo
// @Accept json
// @Produce json
// @Success 200 {object} authResponse
// @Failure 400 {object} code.Failure
// @Router /auth/get [post]
func (h *handler) Auth() core.HandlerFunc {
return func(c core.Context) {
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).JwtSign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
)
return
}
res := new(authResponse)
res.Authorization = tokenString
res.ExpireTime = time.Now().Add(time.Hour * cfg.ExpireDuration).Unix()
c.Payload(res)
}
}

View File

@@ -1,48 +0,0 @@
package demo_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
func (h *handler) Get() core.HandlerFunc {
type request struct {
Name string `uri:"name"`
}
type response struct {
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.Name != "Tom" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.Name != Tom")),
)
return
}
c.Payload(&response{
Name: "Tom",
Job: "Student",
})
}
}

View File

@@ -1,48 +0,0 @@
package demo_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
func (h *handler) Post() core.HandlerFunc {
type request struct {
Name string `form:"name"`
}
type response struct {
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindPostForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.Name != "Jack" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.Name != Jack")),
)
return
}
c.Payload(&response{
Name: "Jack",
Job: "Teacher",
})
}
}

View File

@@ -1,107 +0,0 @@
package demo_handler
import (
"net/http"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"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"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/xinliangnote/go-gin-api/pkg/p"
"go.uber.org/zap"
)
type traceResponse []struct {
Name string `json:"name"` //用户名
Job string `json:"job"` //工作
}
// Trace 示例
// @Summary Trace 示例
// @Description Trace 示例
// @Tags Demo
// @Accept json
// @Produce json
// @Param Authorization header string true "签名"
// @Success 200 {object} traceResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /demo/trace [get]
func (h *handler) Trace() core.HandlerFunc {
return func(c core.Context) {
// 三方请求信息
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.DemoGetRetryVerify),
)
if err != nil {
h.logger.Error("get [demo/get] err", zap.Error(err))
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.CallHTTPError,
code.Text(code.CallHTTPError)).WithErr(err),
)
return
}
// 调试信息
p.Println("res1.Name", res1.Name, p.WithTrace(c.Trace()))
// 三方请求信息
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.DemoPostRetryVerify),
)
if err != nil {
h.logger.Error("post [demo/post] err", zap.Error(err))
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.CallHTTPError,
code.Text(code.CallHTTPError)).WithErr(err),
)
return
}
// 调试信息
p.Println("res2.Name",
res2.Name,
p.WithTrace(c.Trace()),
)
// 执行 SQL 信息
h.userService.GetUserByUserName(c, "test_user")
// 执行 Redis 信息
_ = h.cache.Set("name", "tom", time.Minute*10, cache.WithTrace(c.Trace()))
val, _ := h.cache.Get("name", cache.WithTrace(c.Trace()))
p.Println("redis-name", val, p.WithTrace(c.Trace()))
// 初始化客户端
// client := hello.NewHelloClient(d.grpConn.Conn())
// client.SayHello(grpc.ContextWithValueAndTimeout(c, time.Second*3), &hello.HelloRequest{Name: "Hello World"})
data := &traceResponse{
{
Name: res1.Name,
Job: res1.Job,
},
{
Name: res2.Name,
Job: res2.Job,
},
}
c.Payload(data)
}
}

View File

@@ -1,50 +0,0 @@
package demo_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"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// 示例:支持 get 请求的方法
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
}
type handler struct {
logger *zap.Logger
cache cache.Repo
grpConn grpc.ClientConn
userService user_service.UserService
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) Handler {
return &handler{
logger: logger,
cache: cache,
grpConn: grpConn,
userService: user_service.NewUserService(db, cache),
}
}
func (h *handler) i() {}

View File

@@ -1,77 +0,0 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
type createRequest struct {
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
type createResponse struct {
Id int32 `json:"id"` // 主键ID
}
// 创建用户
// @Summary 创建用户
// @Description 创建用户
// @Tags User
// @Accept json
// @Produce json
// @Param Request body createRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/create [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindJSON(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.UserName == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.UserName = ''")),
)
return
}
createUserData := new(user_service.CreateUserInfo)
createUserData.Mobile = req.Mobile
createUserData.NickName = req.NickName
createUserData.UserName = req.UserName
id, err := h.userService.Create(c, createUserData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserCreateError,
code.Text(code.UserCreateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}

View File

@@ -1,57 +0,0 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteRequest struct {
Id int32 `uri:"id"` // 用户ID
}
type deleteResponse struct {
Id int32 `json:"id"` // 用户主键ID
}
// 删除用户
// @Summary 删除用户
// @Description 删除用户
// @Tags User
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Param Authorization header string true "签名"
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/delete/{id} [patch]
func (h *handler) Delete() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteRequest)
res := new(deleteResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
err := h.userService.Delete(c, req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserUpdateError,
code.Text(code.UserUpdateError)).WithErr(err),
)
return
}
res.Id = req.Id
c.Payload(res)
}
}

View File

@@ -1,65 +0,0 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/ddm"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type detailRequest struct {
UserName string `uri:"username"` // 用户名
}
type detailResponse struct {
Id int32 `json:"id"` // 用户主键ID
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile ddm.Mobile `json:"mobile"` // 手机号(脱敏)
}
// 用户详情
// @Summary 用户详情
// @Description 用户详情
// @Tags User
// @Accept json
// @Produce json
// @Param username path string true "用户名"
// @Param Authorization header string true "签名"
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/info/{username} [get]
func (h *handler) Detail() core.HandlerFunc {
return func(c core.Context) {
req := new(detailRequest)
res := new(detailResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
user, err := h.userService.GetUserByUserName(c, req.UserName)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserSearchError,
code.Text(code.UserSearchError)).WithErr(err),
)
return
}
res.Id = user.Id
res.UserName = user.UserName
res.NickName = user.NickName
res.Mobile = ddm.Mobile(user.Mobile)
c.Payload(res)
}
}

View File

@@ -1,59 +0,0 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateNickNameByIDRequest struct {
Id int32 `json:"id"` // 用户主键ID
NickName string `json:"nick_name"` // 昵称
}
type updateNickNameByIDResponse struct {
Id int32 `json:"id"` // 用户主键ID
}
// 编辑用户 - 通过用户主键ID更新用户昵称
// @Summary 编辑用户 - 通过用户主键ID更新用户昵称
// @Description 编辑用户 - 通过用户主键ID更新用户昵称
// @Tags User
// @Accept json
// @Produce json
// @Param Request body updateNickNameByIDRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} updateNickNameByIDResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/update [put]
func (h *handler) UpdateNickNameByID() core.HandlerFunc {
return func(c core.Context) {
req := new(updateNickNameByIDRequest)
res := new(updateNickNameByIDResponse)
if err := c.ShouldBindJSON(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
err := h.userService.UpdateNickNameByID(c, req.Id, req.NickName)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserUpdateError,
code.Text(code.UserUpdateError)).WithErr(err),
)
return
}
res.Id = req.Id
c.Payload(res)
}
}

View File

@@ -1,53 +0,0 @@
package user_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 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
}
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() {}

View File

@@ -1,15 +0,0 @@
package user_demo_repo
import "time"
// 用户Demo表
//go:generate gormgen -structs UserDemo -input .
type UserDemo struct {
Id int32 // 主键
UserName string // 用户名
NickName string // 昵称
Mobile string // 手机号
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
UpdatedAt time.Time `gorm:"time"` // 更新时间
}

View File

@@ -1,12 +0,0 @@
#### go_gin_api.user_demo
用户Demo表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | user_name | 用户名 | varchar(32) | | NO | | |
| 3 | nick_name | 昵称 | varchar(100) | | NO | | |
| 4 | mobile | 手机号 | varchar(20) | | NO | | |
| 5 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 6 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 7 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |

View File

@@ -1,411 +0,0 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package user_demo_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *UserDemo {
return new(UserDemo)
}
func NewQueryBuilder() *userDemoRepoQueryBuilder {
return new(userDemoRepoQueryBuilder)
}
func (t *UserDemo) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *UserDemo) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *UserDemo) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type userDemoRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *userDemoRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *userDemoRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&UserDemo{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *userDemoRepoQueryBuilder) First(db *gorm.DB) (*UserDemo, error) {
ret := &UserDemo{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *userDemoRepoQueryBuilder) QueryOne(db *gorm.DB) (*UserDemo, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *userDemoRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*UserDemo, error) {
var ret []*UserDemo
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *userDemoRepoQueryBuilder) Limit(limit int) *userDemoRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *userDemoRepoQueryBuilder) Offset(offset int) *userDemoRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIdIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIdNotIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderById(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserNameIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserNameNotIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUserName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "user_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickNameIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickNameNotIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByNickName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "nick_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobileIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobileNotIn(value []string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByMobile(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "mobile "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeletedIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeletedNotIn(value []int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByIsDeleted(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAtIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAtNotIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByCreatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAtIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAtNotIn(value []time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "NOT IN"),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUpdatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}

View File

@@ -1,34 +0,0 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
)
var _ UserService = (*userSer)(nil)
type UserService interface {
// i 为了避免被其他包实现
i()
Create(ctx core.Context, user *CreateUserInfo) (id int32, err error)
UpdateNickNameByID(ctx core.Context, id int32, username string) (err error)
GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error)
Delete(ctx core.Context, id int32) (err error)
}
type userSer struct {
db db.Repo
cache cache.Repo
}
func NewUserService(db db.Repo, cache cache.Repo) UserService {
return &userSer{
db: db,
cache: cache,
}
}
func (u *userSer) i() {}

View File

@@ -1,25 +0,0 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type CreateUserInfo struct {
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
func (u *userSer) Create(ctx core.Context, user *CreateUserInfo) (id int32, err error) {
model := user_demo_repo.NewModel()
model.UserName = user.UserName
model.NickName = user.NickName
model.Mobile = user.Mobile
id, err = model.Create(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}

View File

@@ -1,16 +0,0 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) Delete(ctx core.Context, id int32) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
err = model.Delete(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return nil
}
return nil
}

View File

@@ -1,23 +0,0 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error) {
user, err = user_demo_repo.NewQueryBuilder().
WhereUserName(db_repo.EqualPredicate, username).
QueryOne(u.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return user, err
}
if user == nil {
user = user_demo_repo.NewModel()
}
return user, nil
}

View File

@@ -1,22 +0,0 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) UpdateNickNameByID(ctx core.Context, id int32, nickname string) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"nick_name": nickname,
}
err = model.Updates(u.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
return nil
}

View File

@@ -3,7 +3,6 @@ package resolvers
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/graph/generated"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
@@ -21,17 +20,17 @@ type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type Resolver struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
logger *zap.Logger
cache cache.Repo
//userService user_service.UserService
}
func NewRootResolvers(logger *zap.Logger, db db.Repo, cache cache.Repo) generated.Config {
c := generated.Config{
Resolvers: &Resolver{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
logger: logger,
cache: cache,
//userService: user_service.NewUserService(db, cache),
},
}
return c

View File

@@ -24,18 +24,49 @@ type resource struct {
middles middleware.Middleware
}
func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) (core.Mux, error) {
var openBrowserUri = "http://127.0.0.1:9999"
_, ok := file.IsExists(configs.InitDBLockFile())
if !ok {
openBrowserUri = "http://127.0.0.1:9999/init?init=db"
}
type Server struct {
Mux core.Mux
Db db.Repo
Cache cache.Repo
GrpClient grpc.ClientConn
}
func NewHTTPServer(logger *zap.Logger) (*Server, error) {
if logger == nil {
return nil, errors.New("logger required")
}
r := new(resource)
r.logger = logger
openBrowserUri := "http://127.0.0.1" + configs.ProjectPort()
_, ok := file.IsExists(configs.ProjectInstallFile())
if !ok { // 未安装
openBrowserUri += "/install"
} else { // 已安装
// 初始化 DB
dbRepo, err := db.New()
if err != nil {
logger.Fatal("new db err", zap.Error(err))
}
r.db = dbRepo
// 初始化 Cache
cacheRepo, err := cache.New()
if err != nil {
logger.Fatal("new cache err", zap.Error(err))
}
r.cache = cacheRepo
// 初始化 gRPC client
gRPCRepo, err := grpc.New()
if err != nil {
logger.Fatal("new grpc err", zap.Error(err))
}
r.grpConn = gRPCRepo
}
mux, err := core.New(logger,
core.WithEnableOpenBrowser(openBrowserUri),
core.WithEnableCors(),
@@ -48,13 +79,8 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C
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, db)
r.middles = middleware.New(logger, r.cache, r.db)
// 设置 WEB 路由
setWebRouter(r)
@@ -65,5 +91,11 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C
// 设置 GraphQL 路由
setGraphQLRouter(r)
return mux, nil
s := new(Server)
s.Mux = mux
s.Db = r.db
s.Cache = r.cache
s.GrpClient = r.grpConn
return s, nil
}

View File

@@ -3,47 +3,17 @@ package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/admin_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/authorized_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/config_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/tool_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())
}
// authorized
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
// admin
adminHandler := admin_handler.New(r.logger, r.db, r.cache)
// 登录
// login
login := r.mux.Group("/login", r.middles.Signature())
{
login.POST("/web", adminHandler.Login())
@@ -52,14 +22,16 @@ func setApiRouter(r *resource) {
// api
api := r.mux.Group("/api", core.WrapAuthHandler(r.middles.Token), r.middles.Signature())
{
// authorized
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
api.POST("/authorized", authorizedHandler.Create())
api.GET("/authorized", authorizedHandler.List())
api.PATCH("/authorized/used", authorizedHandler.UpdateUsed())
api.DELETE("/authorized/:id", authorizedHandler.Delete())
api.DELETE("/authorized/:id", core.AliasForRecordMetrics("/api/authorized/info"), authorizedHandler.Delete())
api.POST("/authorized_api", authorizedHandler.CreateAPI())
api.GET("/authorized_api", authorizedHandler.ListAPI())
api.DELETE("/authorized_api/:id", authorizedHandler.DeleteAPI())
api.DELETE("/authorized_api/:id", core.AliasForRecordMetrics("/api/authorized_api/info"), authorizedHandler.DeleteAPI())
api.POST("/admin", adminHandler.Create())
api.GET("/admin", adminHandler.List())
@@ -75,5 +47,10 @@ func setApiRouter(r *resource) {
toolHandler := tool_handler.New(r.logger, r.db, r.cache)
api.GET("/tool/hashids/encode/:id", toolHandler.HashIdsEncode())
api.GET("/tool/hashids/decode/:id", toolHandler.HashIdsDecode())
// config
configHandler := config_handler.New(r.logger, r.db, r.cache)
api.PATCH("/config/email", configHandler.Email())
}
}

View File

@@ -3,38 +3,43 @@ package router
import (
"github.com/xinliangnote/go-gin-api/internal/web/controller/admin_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/authorized_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/configinfo_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/config_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"
"github.com/xinliangnote/go-gin-api/internal/web/controller/install_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/tool_handler"
)
func setWebRouter(r *resource) {
installHandler := install_handler.New(r.logger)
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)
configInfoHandler := config_handler.New(r.logger, r.db, r.cache)
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
toolHandler := tool_handler.New(r.logger, r.db, r.cache)
adminHandler := admin_handler.New(r.logger, r.db, r.cache)
web := r.mux.Group("", r.middles.DisableLog())
{
// 首页侧边栏
// 首页
web.GET("", indexHandler.View())
// 安装
web.GET("/install", installHandler.View())
web.POST("/install/execute", installHandler.Execute())
web.POST("/install/restart", installHandler.Restart())
// 仪表盘
web.GET("/dashboard", dashboardHandler.View())
// 配置信息
web.GET("/configinfo", configInfoHandler.View())
// 代码生成
web.GET("/init", genCodeHandler.InitView())
web.POST("/init_exec", genCodeHandler.InitExecute())
web.GET("/config/email", configInfoHandler.EmailView())
web.GET("/config/code", configInfoHandler.CodeView())
// 代码生成工具
web.GET("/gormgen", genCodeHandler.GormView())
web.POST("/gormgen_exec", genCodeHandler.GormExecute())

View File

@@ -0,0 +1,56 @@
package config_handler
import (
"go/token"
"log"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/dave/dst"
"github.com/dave/dst/decorator"
"github.com/spf13/cast"
)
func (h *handler) CodeView() core.HandlerFunc {
return func(c core.Context) {
fs := token.NewFileSet()
filePath := "./internal/api/code/code.go"
parsedFile, err := decorator.ParseFile(fs, filePath, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", filePath, err)
}
type codes struct {
Code int `json:"code"` // 错误码
Message string `json:"message"` // 错误码信息
}
var constCodes []codes
dst.Inspect(parsedFile, func(n dst.Node) bool {
decl, ok := n.(*dst.GenDecl)
if !ok || decl.Tok != token.CONST {
return true
}
for _, spec := range decl.Specs {
valueSpec, _ok := spec.(*dst.ValueSpec)
if !_ok {
continue
}
codeInt := cast.ToInt(valueSpec.Values[0].(*dst.BasicLit).Value)
constCodes = append(constCodes, codes{
Code: codeInt,
Message: code.Text(codeInt),
})
}
return true
})
c.HTML("config_code", constCodes)
}
}

View File

@@ -0,0 +1,12 @@
package config_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (h *handler) EmailView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("config_email", configs.Get())
}
}

View File

@@ -1,4 +1,4 @@
package configinfo_handler
package config_handler
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -13,7 +13,8 @@ var _ Handler = (*handler)(nil)
type Handler interface {
i()
View() core.HandlerFunc
EmailView() core.HandlerFunc
CodeView() core.HandlerFunc
}
type handler struct {

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"os/exec"
"runtime"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
@@ -25,10 +26,12 @@ func (h *handler) GormExecute() core.HandlerFunc {
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
// runtime.GOOS = linux or darwin
command := exec.Command("/bin/bash", "-c", shellPath)
// windows 版本
//command := exec.Command("cmd", "/C", shellPath)
if runtime.GOOS == "windows" {
command = exec.Command("cmd", "/C", shellPath)
}
var stderr bytes.Buffer
command.Stderr = &stderr

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"os/exec"
"runtime"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
@@ -21,10 +22,13 @@ func (h *handler) HandlerExecute() core.HandlerFunc {
}
shellPath := fmt.Sprintf("./scripts/handlergen.sh %s", req.Name)
command := exec.Command("/bin/bash", "-c", shellPath) //初始化 Cmd
// windows 版本
//command := exec.Command("cmd", "/C", shellPath)
// runtime.GOOS = linux or darwin
command := exec.Command("/bin/bash", "-c", shellPath)
if runtime.GOOS == "windows" {
command = exec.Command("cmd", "/C", shellPath)
}
var stderr bytes.Buffer
command.Stderr = &stderr

View File

@@ -1,33 +0,0 @@
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))
}
}

View File

@@ -1,9 +0,0 @@
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)
}
}

View File

@@ -13,9 +13,6 @@ var _ Handler = (*handler)(nil)
type Handler interface {
i()
InitView() core.HandlerFunc
InitExecute() core.HandlerFunc
HandlerView() core.HandlerFunc
HandlerExecute() core.HandlerFunc

View File

@@ -0,0 +1,204 @@
package install_handler
import (
"fmt"
"net/http"
"os"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/web/controller/install_handler/mysql_table"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/go-redis/redis/v7"
"github.com/spf13/cast"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type initExecuteRequest struct {
RedisAddr string `form:"redis_addr"` // 连接地址例如127.0.0.1:6379
RedisPass string `form:"redis_pass"` // 连接密码
RedisDb string `form:"redis_db"` // 连接 db
MySQLAddr string `form:"mysql_addr"`
MySQLUser string `form:"mysql_user"`
MySQLPass string `form:"mysql_pass"`
MySQLName string `form:"mysql_name"`
}
func (h *handler) Execute() core.HandlerFunc {
return func(c core.Context) {
req := new(initExecuteRequest)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
outPutString := ""
cfg := configs.Get()
redisClient := redis.NewClient(&redis.Options{
Addr: req.RedisAddr,
Password: req.RedisPass,
DB: cast.ToInt(req.RedisDb),
MaxRetries: cfg.Redis.MaxRetries,
PoolSize: cfg.Redis.PoolSize,
MinIdleConns: cfg.Redis.MinIdleConns,
})
if err := redisClient.Ping().Err(); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigRedisConnectError,
code.Text(code.ConfigRedisConnectError)).WithErr(err),
)
return
}
defer redisClient.Close()
outPutString += "已检测 Redis 配置可用。\n"
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
req.MySQLUser,
req.MySQLPass,
req.MySQLAddr,
req.MySQLName,
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 {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLConnectError,
code.Text(code.ConfigMySQLConnectError)).WithErr(err),
)
return
}
db.Set("gorm:table_options", "CHARSET=utf8mb4")
dbClient, _ := db.DB()
defer dbClient.Close()
outPutString += "已检测 MySQL 配置可用。\n"
viper.SetConfigName(env.Active().Value() + "_configs")
viper.SetConfigType("toml")
viper.AddConfigPath("./configs")
viper.Set("redis.addr", req.RedisAddr)
viper.Set("redis.pass", req.RedisPass)
viper.Set("redis.db", req.RedisDb)
viper.Set("mysql.read.addr", req.MySQLAddr)
viper.Set("mysql.read.user", req.MySQLUser)
viper.Set("mysql.read.pass", req.MySQLPass)
viper.Set("mysql.read.name", req.MySQLName)
viper.Set("mysql.write.addr", req.MySQLAddr)
viper.Set("mysql.write.user", req.MySQLUser)
viper.Set("mysql.write.pass", req.MySQLPass)
viper.Set("mysql.write.name", req.MySQLName)
if viper.WriteConfig() != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigSaveError,
code.Text(code.ConfigSaveError)).WithErr(err),
)
return
}
outPutString += "配置项 Redis、MySQL 配置成功。\n"
if err = db.Exec(mysql_table.CreateAuthorizedTableSql()).Error; err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLInstallError,
"MySQL "+err.Error()).WithErr(err),
)
return
}
outPutString += "初始化 MySQL 数据表authorized 成功。\n"
if err = db.Exec(mysql_table.CreateAuthorizedTableDataSql()).Error; err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLInstallError,
"MySQL "+err.Error()).WithErr(err),
)
return
}
outPutString += "初始化 MySQL 数据表authorized 默认数据成功。\n"
if err = db.Exec(mysql_table.CreateAuthorizedAPITableSql()).Error; err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLInstallError,
"MySQL "+err.Error()).WithErr(err),
)
return
}
outPutString += "初始化 MySQL 数据表authorized_api 成功。\n"
if err = db.Exec(mysql_table.CreateAuthorizedAPITableDataSql()).Error; err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLInstallError,
"MySQL "+err.Error()).WithErr(err),
)
return
}
outPutString += "初始化 MySQL 数据表authorized_api 默认数据成功。\n"
if err = db.Exec(mysql_table.CreateAdminTableSql()).Error; err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLInstallError,
"MySQL "+err.Error()).WithErr(err),
)
return
}
outPutString += "初始化 MySQL 数据表admin 成功。\n"
if err = db.Exec(mysql_table.CreateAdminTableDataSql()).Error; err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLInstallError,
"MySQL "+err.Error()).WithErr(err),
)
return
}
outPutString += "初始化 MySQL 数据表admin 默认数据成功。\n"
// 生成 install 完成标识
f, err := os.Create(configs.ProjectInstallFile())
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ConfigMySQLInstallError,
"create lock file err: "+err.Error()).WithErr(err),
)
return
}
defer f.Close()
c.Payload(outPutString)
}
}

View File

@@ -0,0 +1,37 @@
package install_handler
import (
"bytes"
"os/exec"
"runtime"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (h *handler) Restart() core.HandlerFunc {
return func(c core.Context) {
shellPath := "./scripts/restart.sh"
// runtime.GOOS = linux or darwin
command := exec.Command("/bin/bash", "-c", shellPath)
if runtime.GOOS == "windows" {
command = exec.Command("cmd", "/C", shellPath)
}
var stderr bytes.Buffer
command.Stderr = &stderr
outPutString := ""
output, err := command.Output()
if err != nil {
outPutString += stderr.String()
c.Payload(outPutString)
return
}
outPutString += string(output)
c.Payload(outPutString)
}
}

View File

@@ -1,4 +1,4 @@
package configinfo_handler
package install_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
@@ -7,6 +7,6 @@ import (
func (h *handler) View() core.HandlerFunc {
return func(c core.Context) {
c.HTML("configinfo", configs.Get())
c.HTML("install_view", configs.Get())
}
}

View File

@@ -0,0 +1,29 @@
package install_handler
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
i()
View() core.HandlerFunc
Execute() core.HandlerFunc
Restart() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
}
func New(logger *zap.Logger) Handler {
return &handler{
logger: logger,
}
}
func (h *handler) i() {}

View File

@@ -1,4 +1,4 @@
package mysql
package mysql_table
//CREATE TABLE `admin` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',

View File

@@ -1,4 +1,4 @@
package mysql
package mysql_table
//CREATE TABLE `authorized` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',

View File

@@ -1,4 +1,4 @@
package mysql
package mysql_table
//CREATE TABLE `authorized_api` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
@@ -11,7 +11,7 @@ package mysql
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
//`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',
//PRIMARY KEY (`id`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权接口地址';
//) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权接口地址';
func CreateAuthorizedAPITableSql() (sql string) {
sql = "CREATE TABLE `authorized_api` ("
@@ -25,7 +25,7 @@ func CreateAuthorizedAPITableSql() (sql string) {
sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',"
sql += "`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',"
sql += "PRIMARY KEY (`id`)"
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权的调用方表';"
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权接口地址表';"
return
}

View File

@@ -1,4 +1,4 @@
package mysql
package mysql_table
//CREATE TABLE `user_demo` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',

71
main.go
View File

@@ -7,9 +7,6 @@ import (
"time"
"github.com/xinliangnote/go-gin-api/configs"
"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"
@@ -43,33 +40,15 @@ func main() {
}
defer loggers.Sync()
// 初始化 DB
dbRepo, err := db.New()
if err != nil {
loggers.Fatal("new db err", zap.Error(err))
}
// 初始化 Cache
cacheRepo, err := cache.New()
if err != nil {
loggers.Fatal("new cache err", zap.Error(err))
}
// 初始化 gRPC client
gRPCRepo, err := grpc.New()
if err != nil {
loggers.Fatal("new grpc err", zap.Error(err))
}
// 初始化 HTTP 服务
mux, err := router.NewHTTPMux(loggers, dbRepo, cacheRepo, gRPCRepo)
s, err := router.NewHTTPServer(loggers)
if err != nil {
panic(err)
}
server := &http.Server{
Addr: configs.ProjectPort(),
Handler: mux,
Handler: s.Mux,
}
go func() {
@@ -94,35 +73,41 @@ func main() {
// 关闭 db
func() {
if err := dbRepo.DbWClose(); err != nil {
loggers.Error("dbw close err", zap.Error(err))
} else {
loggers.Info("dbw close success")
}
if s.Db != nil {
if err := s.Db.DbWClose(); err != nil {
loggers.Error("dbw close err", zap.Error(err))
} else {
loggers.Info("dbw close success")
}
if err := dbRepo.DbRClose(); err != nil {
loggers.Error("dbr close err", zap.Error(err))
} else {
loggers.Info("dbr close success")
if err := s.Db.DbRClose(); err != nil {
loggers.Error("dbr close err", zap.Error(err))
} else {
loggers.Info("dbr close success")
}
}
},
// 关闭 cache
func() {
if err := cacheRepo.Close(); err != nil {
loggers.Error("cache close err", zap.Error(err))
} else {
loggers.Info("cache close success")
if s.Cache != nil {
if err := s.Cache.Close(); err != nil {
loggers.Error("cache close err", zap.Error(err))
} else {
loggers.Info("cache close success")
}
}
},
// 关闭 gRPC client
//func() {
// if err := gRPCRepo.Conn().Close(); err != nil {
// loggers.Error("gRPC client close err", zap.Error(err))
// } else {
// loggers.Info("gRPC client close success")
// }
//},
func() {
if s.GrpClient != nil {
if err := s.GrpClient.Conn().Close(); err != nil {
loggers.Error("gRPC client close err", zap.Error(err))
} else {
loggers.Info("gRPC client close success")
}
}
},
)
}

16
scripts/restart.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
shellExit()
{
if [ $1 -eq 1 ]; then
printf "\nfailed!!!\n\n"
exit 1
fi
}
printf "\nRestart server port:9999 \n\n"
lsof -i:9999 | grep LISTEN | awk '{print $2}' | xargs kill -s SIGINT && go run ./main.go -env fat
shellExit $?
printf "\nDone.\n\n"