This commit is contained in:
新亮
2021-05-15 13:01:30 +08:00
parent ec3c9f0ccf
commit a6947b59ac
60 changed files with 1278 additions and 283 deletions

View File

@@ -32,7 +32,8 @@
<th>手机号</th>
<th>创建日期</th>
<th>更新日期</th>
<th style="text-align: center; ">状态</th>
<th style="text-align: center; ">可用状态</th>
<th style="text-align: center; ">在线状态</th>
<th style="text-align: center; ">操作</th>
</tr>
</thead>
@@ -102,6 +103,7 @@
$.each(data.list, function (index, value) {
var showUsedBadge = "";
var showOnlineBadge = "";
var optionUsedName = "";
if (value.is_used === 1) {
@@ -114,6 +116,14 @@
showUsedBadge = '<span class="badge badge-danger">禁用</span></td>'
}
if (value.is_online === 1) {
showOnlineBadge = '<span class="badge btn-primary">在线</span></td>'
}
if (value.is_online === -1) {
showOnlineBadge = '<span class="badge btn-secondary">离线</span></td>'
}
const tr = '<tr>\n' +
'<td>' + value.id + '</td>\n' +
'<td>' + value.username + '</td>\n' +
@@ -122,6 +132,7 @@
'<td>' + value.created_at + '</td>\n' +
'<td>' + value.updated_at + '</td>\n' +
'<td style="text-align: center; ">' + showUsedBadge + '</td>\n' +
'<td style="text-align: center; ">' + showOnlineBadge + '</td>\n' +
'<td style="text-align: center; ">\n' +
'<div class="btn-group">\n' +
' <a class="btn btn-xs btn-default btn-option" href="#!" title=""\n' +
@@ -134,6 +145,9 @@
' <a class="btn btn-xs btn-default btn-menu" href="#!" title=""\n' +
' data-id="' + value.hashid + '"' +
' data-toggle="tooltip" data-original-title="菜单授权">菜单授权</a>\n' +
' <a class="btn btn-xs btn-default btn-offline" href="#!" title=""\n' +
' data-id="' + value.hashid + '"' +
' data-toggle="tooltip" data-original-title="下线">下线</a>\n' +
' <a class="btn btn-xs btn-default btn-confirm" href="#!" title=""\n' +
' data-id="' + value.hashid + '"' +
' data-toggle="tooltip" data-original-title="删除">删除</a>\n' +
@@ -280,6 +294,57 @@
location.href = "/admin/action/" + $(this).attr('data-id');
});
// 下线
$(document).on('click', '.btn-offline', function () {
const id = $(this).attr('data-id');
$.confirm({
title: '谨慎操作',
content: '确认要 <strong style="color: red">下线</strong> 吗?',
icon: 'mdi mdi-alert',
animation: 'scale',
closeAnimation: 'zoom',
buttons: {
okay: {
text: '确认',
keys: ['enter'],
btnClass: 'btn-orange',
action: function () {
AjaxForm(
"PATCH",
'/api/admin/offline',
{id: id},
function () {},
function (data) {
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '编号:' + data.id + ' 已下线。',
buttons: {
okay: {
text: '关闭',
action: function () {
location.reload();
}
}
}
});
},
function (response) {
AjaxError(response);
}
);
}
},
cancel: {
text: '取消',
keys: ['ctrl', 'shift'],
}
}
});
});
// 删除
$(document).on('click', '.btn-confirm', function () {
const id = $(this).attr('data-id');

View File

@@ -15,7 +15,7 @@
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">项目信息</div>
<div class="card-title">项目信息 <span class="badge badge-pill badge-warning">{{ .ProjectVersion }}</span> </div>
</header>
<div class="card-body">
<p>操作系统:{{ .GoOS }} <span class="badge badge-brown"> {{ .GoArch }} </span> <span class="badge badge-info"> {{ .GoVersion }} </span></p>

View File

@@ -22,82 +22,29 @@
<div class="lyear-layout-container">
<!--左侧导航-->
<aside class="lyear-layout-sidebar">
<!-- logo -->
<div id="logo" class="sidebar-header">
<a href="/">
<img src="bootstrap/images/logo-sidebar.png"/>
</a>
</div>
<div class="lyear-layout-sidebar-info lyear-scroll">
<div class="lyear-layout-sidebar-info lyear-scroll">
<nav class="sidebar-main">
<ul class="nav-drawer">
<li class="nav-item active"> <a class="multitabs" href="/dashboard"><i class="mdi mdi-home"></i> <span>仪表盘</span></a> </li>
<li class="nav-item 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="/generator/gorm">生成数据表 CURD</a> </li>
<li> <a class="multitabs" href="/generator/handler">生成控制器方法</a> </li>
</ul>
</li>
<li class="nav-item nav-item-has-subnav">
<a href="javascript:void(0)"><i class="mdi mdi-playlist-check"></i> <span>授权调用方</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/authorized/list">调用方</a> </li>
<li> <a class="multitabs" href="/authorized/demo">使用说明</a> </li>
</ul>
</li>
<li class="nav-item nav-item-has-subnav">
<a href="javascript:void(0)"><i class="mdi mdi-account"></i> <span>系统管理员</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/admin/list">管理员</a> </li>
<li> <a class="multitabs" href="/admin/menu">菜单管理</a> </li>
</ul>
</li>
<li class="nav-item nav-item-has-subnav">
<a href="javascript:void(0)"><i class="mdi mdi-database-search"></i> <span>查询小助手</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/tool/cache">查询缓存</a> </li>
<li> <a class="multitabs" href="/tool/data">查询数据</a> </li>
</ul>
</li>
<li class="nav-item nav-item-has-subnav">
<a href="javascript:void(0)"><i class="mdi mdi-tools"></i> <span>实用工具箱</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/upgrade">服务升级</a> </li>
<li> <a class="multitabs" href="/tool/hashids">Hashids</a> </li>
<li> <a class="multitabs" href="/tool/logs">调用日志</a> </li>
<li> <a target="_blank" href="/swagger/index.html">接口文档</a> </li>
<li> <a target="_blank" href="/graphql">GraphQL</a> </li>
<li> <a target="_blank" href="/metrics">接口指标</a> </li>
</ul>
</li>
</ul>
</nav>
</div>
</aside>
<!--End 左侧导航-->
<!--头部信息-->
<header class="lyear-layout-header">
<nav class="navbar">
<div class="navbar-left">
<div class="lyear-aside-toggler">
<span class="lyear-toggler-bar"></span>
@@ -105,7 +52,7 @@
<span class="lyear-toggler-bar"></span>
</div>
</div>
<ul class="navbar-right d-flex align-items-center">
<!--切换主题配色-->
@@ -152,28 +99,28 @@
<span class="inverse">
<input type="radio" name="header_bg" value="default" id="header_bg_1" checked>
<label for="header_bg_1"></label>
</span>
<span>
</span>
<span>
<input type="radio" name="header_bg" value="color_2" id="header_bg_2">
<label for="header_bg_2"></label>
</span>
<span>
</span>
<span>
<input type="radio" name="header_bg" value="color_3" id="header_bg_3">
<label for="header_bg_3"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_4" id="header_bg_4">
<label for="header_bg_4"></label>
</span>
<span>
</span>
<span>
<input type="radio" name="header_bg" value="color_5" id="header_bg_5">
<label for="header_bg_5"></label>
</span>
<span>
</span>
<span>
<input type="radio" name="header_bg" value="color_6" id="header_bg_6">
<label for="header_bg_6"></label>
</span>
<span>
</span>
<span>
<input type="radio" name="header_bg" value="color_7" id="header_bg_7">
<label for="header_bg_7"></label>
</span>
@@ -247,17 +194,17 @@
</li>
</ul>
</nav>
</header>
<!--End 头部信息-->
<!--页面主要内容-->
<main class="lyear-layout-content">
<div id="iframe-content"></div>
</main>
<!--End 页面主要内容-->
</div>
@@ -281,6 +228,41 @@
function () {},
function (data) {
$("#nickname").html(data.nickname);
$(".nav-drawer").html("");
let li = '<li class="nav-item active"><a class="multitabs" href="/dashboard"><i class="mdi mdi-home"></i> <span>仪表盘</span></a></li>';
if (data.menu.length > 0) {
let newArr = [];
data.menu.forEach(function (v) {
if (v.pid === 0) {
v.children = [];
newArr.push(v)
}
});
data.menu.forEach(function (v) {
newArr.forEach(function (item) {
if (v.pid === item.id) {
item.children.push(v)
}
})
});
$.each(newArr, function (index, value) {
li += '<li class="nav-item nav-item-has-subnav">';
li += '<a href="javascript:void(0)"><i class="mdi '+ value.icon +'"></i> <span>'+ value.name +'</span></a>';
li += '<ul class="nav nav-subnav">';
value.children.forEach(function (item) {
li += '<li> <a class="multitabs" href="'+ item.link +'"> '+ item.name +' </a> </li>';
});
li += '</ul></li>';
});
$(".nav-drawer").html(li);
}
},
function (response) {
AjaxError(response);

View File

@@ -18,34 +18,74 @@
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-12">
<div class="col-lg-6">
<div class="card">
<header class="card-header">
<div class="card-title">服务升级</div>
<div class="card-title">无版本号情况,服务升级</div>
</header>
<div class="card-body">
<p class="h6">发现代码运行报错或缺少表字段,如何进行服务初始化?</p>
<p>1、删除根目录文件
<mark>{{ .LockFile }}</mark>
</p>
<p>2、重新启动服务。</p>
<hr/>
<p class="h6">发现 GitHub 仓库更新了新代码,如何进行服务升级?</p>
<p>1、源代码升级
<mark>拉取最新代码,覆盖旧版本代码即可。</mark>
</p>
<p>2、数据表升级</p>
{{range $key, $value := .List}}
<p style="margin-left: 24px;">
<i class="mdi mdi-checkbox-marked-circle"></i> MySQL 数据表:{{$value.TableName}}
{{if eq $value.IsHave 1}} <font class="text-success">已存在</font>
{{else}} <font class="text-danger">存在</font>
{{end}}
<table style="margin-left: 24px;" class="table">
<thead>
<tr>
<th>数据表</th>
<th>是否存在</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{range $key, $value := .List}}
<tr>
<td>{{$value.TableName}}</td>
<td>
{{if eq $value.IsHave 1}} <font class="text-success">已存在</font>
{{else}} <font class="text-danger">不存在</font>
{{end}}
</td>
<td>
<button class="btn btn-xs btn-info upgrade" data-op="table"
data-table="{{$value.TableName}}">创建表结构
</button>
<button class="btn btn-xs btn-cyan upgrade" data-op="table_data"
data-table="{{$value.TableName}}">初始化数据
</button>
</td>
</tr>
{{end}}
</tbody>
</table>
<button class="btn btn-xs btn-info upgrade" data-op="table"
data-table="{{$value.TableName}}">创建表结构
</button>
<button class="btn btn-xs btn-cyan upgrade" data-op="table_data"
data-table="{{$value.TableName}}">初始化数据
</button>
</p>
{{end}}
</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">
<p class="h6">
<span class="badge badge-pill badge-warning">v1.26</span>
->
<span class="badge badge-pill badge-warning">v1.27</span>
</p>
<p>......</p>
<hr/>
</div>
</div>
</div>

View File

@@ -15,7 +15,7 @@
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-6">
<div class="col-lg-5">
<div class="card">
<div class="card-header">
<div class="card-title">配置菜单栏</div>
@@ -64,7 +64,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="col-lg-7">
<div class="card">
<header class="card-header">
<div class="card-title">菜单栏列表</div>
@@ -127,7 +127,14 @@
field: 'name',
title: '名称',
formatter: function (value, row, index) {
return '<i class="mdi ' + row.icon + '"></i>' + row.name ;
return '<i class="mdi ' + row.icon + '"></i>' + row.name;
}
},
{
field: 'sort',
title: '排序',
formatter: function (value, row, index) {
return '<input type="text" value="'+ row.sort +'" data-id="' + row.hashid + '" data-name="' + row.name + '" class="form-control sort" style="width:60px;">';
}
},
{
@@ -302,6 +309,59 @@
);
}
$(document).on('blur', '.sort', function () {
const hashid = $(this).attr("data-id");
const name = $(this).attr("data-name");
const val = $(this).val();
$.confirm({
title: '谨慎操作',
content: '确认要将 ' + name + ' 的排序设置为 <strong style="color: red">' + val + '</strong> 吗?',
icon: 'mdi mdi-alert',
animation: 'scale',
closeAnimation: 'zoom',
buttons: {
okay: {
text: '确认',
keys: ['enter'],
btnClass: 'btn-orange',
action: function () {
AjaxForm(
"PATCH",
"/api/menu/sort",
{id: hashid, sort: val},
function () {
},
function () {
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '菜单:' + name + '排序成功。',
buttons: {
okay: {
text: '关闭',
action: function () {
location.reload();
}
}
}
});
},
function (response) {
AjaxError(response);
}
);
}
},
cancel: {
text: '取消',
keys: ['ctrl', 'shift'],
}
}
});
});
$(document).on('click', '.customSwitch', function () {
let state = $(this).attr("state");
const hashid = $(this).attr("hashid");

View File

@@ -1,7 +1,6 @@
package configs
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/pkg/env"
@@ -90,19 +89,3 @@ func init() {
func Get() Config {
return *config
}
func ProjectName() string {
return "go-gin-api"
}
func ProjectPort() string {
return ":9999"
}
func ProjectLogFile() string {
return fmt.Sprintf("./logs/%s-access.log", ProjectName())
}
func ProjectInstallFile() string {
return "INSTALL.lock"
}

36
configs/constants.go Normal file
View File

@@ -0,0 +1,36 @@
package configs
const (
// 项目版本
ProjectVersion = "v1.2.6"
// 项目名称
ProjectName = "go-gin-api"
// 项目端口
ProjectPort = ":9999"
// 项目日志存放文件
ProjectLogFile = "./logs/" + ProjectName + "-access.log"
// 项目安装完成标识
ProjectInstallMark = "INSTALL.lock"
// 登录验证 TokenHeader 中传递的参数
LoginToken = "Token"
// 签名验证 TokenHeader 中传递的参数
SignToken = "Authorization"
// 签名验证 DateHeader 中传递的参数
SignTokenDate = "Authorization-Date"
// Redis Key 前缀 - 防止重复提交
RedisKeyPrefixRequestID = ProjectName + ":request-id:"
// Redis Key 前缀 - 登录用户信息
RedisKeyPrefixLoginUser = ProjectName + ":login-user:"
// Redis Key 前缀 - 签名验证信息
RedisKeyPrefixSignature = ProjectName + ":signature:"
)

View File

@@ -177,9 +177,9 @@ var doc = `{
},
"/api/admin/login": {
"post": {
"description": "管理员登",
"description": "管理员登",
"consumes": [
"multipart/form-data"
"application/json"
],
"produces": [
"application/json"
@@ -187,28 +187,12 @@ var doc = `{
"tags": [
"API.admin"
],
"summary": "管理员登",
"parameters": [
{
"type": "string",
"description": "用户名",
"name": "username",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "密码",
"name": "password",
"in": "formData",
"required": true
}
],
"summary": "管理员登",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/admin_handler.loginResponse"
"$ref": "#/definitions/admin_handler.logoutResponse"
}
},
"400": {
@@ -348,6 +332,44 @@ var doc = `{
}
}
},
"/api/admin/offline": {
"patch": {
"description": "下线管理员",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"API.admin"
],
"summary": "下线管理员",
"parameters": [
{
"type": "string",
"description": "Hashid",
"name": "id",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/admin_handler.offlineResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/admin/reset_password/{id}": {
"patch": {
"description": "重置密码",
@@ -928,11 +950,56 @@ var doc = `{
}
}
},
"/api/menu/sort": {
"patch": {
"description": "更新菜单排序",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"API.menu"
],
"summary": "更新菜单排序",
"parameters": [
{
"type": "string",
"description": "Hashid",
"name": "id",
"in": "formData",
"required": true
},
{
"type": "integer",
"description": "排序",
"name": "sort",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/menu_handler.updateSortResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/menu/used": {
"patch": {
"description": "更新菜单为启用/禁用",
"consumes": [
"application/json"
"multipart/form-data"
],
"produces": [
"application/json"
@@ -1467,6 +1534,13 @@ var doc = `{
"admin_handler.detailResponse": {
"type": "object",
"properties": {
"menu": {
"description": "菜单栏",
"type": "array",
"items": {
"$ref": "#/definitions/admin_service.ListMyMenuData"
}
},
"mobile": {
"description": "手机号",
"type": "string"
@@ -1514,6 +1588,10 @@ var doc = `{
"description": "ID",
"type": "integer"
},
"is_online": {
"description": "是否在线 1:是 -1:否",
"type": "integer"
},
"is_used": {
"description": "是否启用 1:是 -1:否",
"type": "integer"
@@ -1601,6 +1679,15 @@ var doc = `{
}
}
},
"admin_handler.offlineResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"admin_handler.resetPasswordResponse": {
"type": "object",
"properties": {
@@ -1640,6 +1727,31 @@ var doc = `{
}
}
},
"admin_service.ListMyMenuData": {
"type": "object",
"properties": {
"icon": {
"description": "图标",
"type": "string"
},
"id": {
"description": "ID",
"type": "integer"
},
"link": {
"description": "链接地址",
"type": "string"
},
"name": {
"description": "菜单名称",
"type": "string"
},
"pid": {
"description": "父类ID",
"type": "integer"
}
}
},
"authorized_handler.createAPIResponse": {
"type": "object",
"properties": {
@@ -1972,6 +2084,10 @@ var doc = `{
"pid": {
"description": "父类ID",
"type": "integer"
},
"sort": {
"description": "排序",
"type": "integer"
}
}
},
@@ -1986,6 +2102,15 @@ var doc = `{
}
}
},
"menu_handler.updateSortResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"menu_handler.updateUsedResponse": {
"type": "object",
"properties": {

View File

@@ -160,9 +160,9 @@
},
"/api/admin/login": {
"post": {
"description": "管理员登",
"description": "管理员登",
"consumes": [
"multipart/form-data"
"application/json"
],
"produces": [
"application/json"
@@ -170,28 +170,12 @@
"tags": [
"API.admin"
],
"summary": "管理员登",
"parameters": [
{
"type": "string",
"description": "用户名",
"name": "username",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "密码",
"name": "password",
"in": "formData",
"required": true
}
],
"summary": "管理员登",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/admin_handler.loginResponse"
"$ref": "#/definitions/admin_handler.logoutResponse"
}
},
"400": {
@@ -331,6 +315,44 @@
}
}
},
"/api/admin/offline": {
"patch": {
"description": "下线管理员",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"API.admin"
],
"summary": "下线管理员",
"parameters": [
{
"type": "string",
"description": "Hashid",
"name": "id",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/admin_handler.offlineResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/admin/reset_password/{id}": {
"patch": {
"description": "重置密码",
@@ -911,11 +933,56 @@
}
}
},
"/api/menu/sort": {
"patch": {
"description": "更新菜单排序",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"API.menu"
],
"summary": "更新菜单排序",
"parameters": [
{
"type": "string",
"description": "Hashid",
"name": "id",
"in": "formData",
"required": true
},
{
"type": "integer",
"description": "排序",
"name": "sort",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/menu_handler.updateSortResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/menu/used": {
"patch": {
"description": "更新菜单为启用/禁用",
"consumes": [
"application/json"
"multipart/form-data"
],
"produces": [
"application/json"
@@ -1450,6 +1517,13 @@
"admin_handler.detailResponse": {
"type": "object",
"properties": {
"menu": {
"description": "菜单栏",
"type": "array",
"items": {
"$ref": "#/definitions/admin_service.ListMyMenuData"
}
},
"mobile": {
"description": "手机号",
"type": "string"
@@ -1497,6 +1571,10 @@
"description": "ID",
"type": "integer"
},
"is_online": {
"description": "是否在线 1:是 -1:否",
"type": "integer"
},
"is_used": {
"description": "是否启用 1:是 -1:否",
"type": "integer"
@@ -1584,6 +1662,15 @@
}
}
},
"admin_handler.offlineResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"admin_handler.resetPasswordResponse": {
"type": "object",
"properties": {
@@ -1623,6 +1710,31 @@
}
}
},
"admin_service.ListMyMenuData": {
"type": "object",
"properties": {
"icon": {
"description": "图标",
"type": "string"
},
"id": {
"description": "ID",
"type": "integer"
},
"link": {
"description": "链接地址",
"type": "string"
},
"name": {
"description": "菜单名称",
"type": "string"
},
"pid": {
"description": "父类ID",
"type": "integer"
}
}
},
"authorized_handler.createAPIResponse": {
"type": "object",
"properties": {
@@ -1955,6 +2067,10 @@
"pid": {
"description": "父类ID",
"type": "integer"
},
"sort": {
"description": "排序",
"type": "integer"
}
}
},
@@ -1969,6 +2085,15 @@
}
}
},
"menu_handler.updateSortResponse": {
"type": "object",
"properties": {
"id": {
"description": "主键ID",
"type": "integer"
}
}
},
"menu_handler.updateUsedResponse": {
"type": "object",
"properties": {

View File

@@ -13,6 +13,11 @@ definitions:
type: object
admin_handler.detailResponse:
properties:
menu:
description: 菜单栏
items:
$ref: '#/definitions/admin_service.ListMyMenuData'
type: array
mobile:
description: 手机号
type: string
@@ -46,6 +51,9 @@ definitions:
id:
description: ID
type: integer
is_online:
description: 是否在线 1:是 -1:否
type: integer
is_used:
description: 是否启用 1:是 -1:否
type: integer
@@ -105,6 +113,12 @@ definitions:
description: 用户账号
type: string
type: object
admin_handler.offlineResponse:
properties:
id:
description: 主键ID
type: integer
type: object
admin_handler.resetPasswordResponse:
properties:
id:
@@ -132,6 +146,24 @@ definitions:
description: 父类ID
type: integer
type: object
admin_service.ListMyMenuData:
properties:
icon:
description: 图标
type: string
id:
description: ID
type: integer
link:
description: 链接地址
type: string
name:
description: 菜单名称
type: string
pid:
description: 父类ID
type: integer
type: object
authorized_handler.createAPIResponse:
properties:
id:
@@ -364,6 +396,9 @@ definitions:
pid:
description: 父类ID
type: integer
sort:
description: 排序
type: integer
type: object
menu_handler.listResponse:
properties:
@@ -372,6 +407,12 @@ definitions:
$ref: '#/definitions/menu_handler.listData'
type: array
type: object
menu_handler.updateSortResponse:
properties:
id:
description: 主键ID
type: integer
type: object
menu_handler.updateUsedResponse:
properties:
id:
@@ -593,31 +634,20 @@ paths:
/api/admin/login:
post:
consumes:
- multipart/form-data
description: 管理员登
parameters:
- description: 用户名
in: formData
name: username
required: true
type: string
- description: 密码
in: formData
name: password
required: true
type: string
- application/json
description: 管理员登
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.loginResponse'
$ref: '#/definitions/admin_handler.logoutResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 管理员登
summary: 管理员登
tags:
- API.admin
/api/admin/menu:
@@ -705,6 +735,31 @@ paths:
summary: 修改个人信息
tags:
- API.admin
/api/admin/offline:
patch:
consumes:
- multipart/form-data
description: 下线管理员
parameters:
- description: Hashid
in: formData
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.offlineResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 下线管理员
tags:
- API.admin
/api/admin/reset_password/{id}:
patch:
consumes:
@@ -1115,10 +1170,40 @@ paths:
summary: 菜单详情
tags:
- API.menu
/api/menu/sort:
patch:
consumes:
- multipart/form-data
description: 更新菜单排序
parameters:
- description: Hashid
in: formData
name: id
required: true
type: string
- description: 排序
in: formData
name: sort
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/menu_handler.updateSortResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 更新菜单排序
tags:
- API.menu
/api/menu/used:
patch:
consumes:
- application/json
- multipart/form-data
description: 更新菜单为启用/禁用
parameters:
- description: Hashid

View File

@@ -17,6 +17,7 @@ const (
ResubmitMsg = 10107
HashIdsDecodeError = 10108
SignatureError = 10109
RBACError = 10110
// 业务模块级错误码
// 用户模块
@@ -47,6 +48,8 @@ const (
AdminModifyPersonalInfoError = 20309
AdminMenuListError = 20310
AdminMenuCreateError = 20311
AdminOfflineError = 20312
AdminDetailError = 20313
// 配置
ConfigEmailError = 20401
@@ -83,6 +86,7 @@ var codeText = map[int]string{
ResubmitMsg: "请勿重复提交",
HashIdsDecodeError: "ID 参数有误",
SignatureError: "Signature Error",
RBACError: "暂无权限,请联系管理开通权限",
IllegalUserName: "非法用户名",
UserCreateError: "创建用户失败",
@@ -109,6 +113,8 @@ var codeText = map[int]string{
AdminModifyPersonalInfoError: "修改个人信息失败",
AdminMenuListError: "获取管理员菜单授权列表失败",
AdminMenuCreateError: "管理员菜单授权失败",
AdminOfflineError: "下线管理员失败",
AdminDetailError: "获取个人信息失败",
ConfigEmailError: "修改邮箱配置失败",
ConfigSaveError: "写入配置文件失败",

View File

@@ -1,20 +1,25 @@
package admin_handler
import (
"encoding/json"
"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/api/service/admin_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/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
type detailResponse struct {
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
Menu []admin_service.ListMyMenuData `json:"menu"` // 菜单栏
}
// Detail 管理员详情
@@ -38,15 +43,29 @@ func (h *handler) Detail() core.HandlerFunc {
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
code.AdminDetailError,
code.Text(code.AdminDetailError)).WithErr(err),
)
return
}
menuCacheData, err := h.cache.Get(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(searchOneData.Id)+":menu", cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminDetailError,
code.Text(code.AdminDetailError)).WithErr(err),
)
return
}
var menuData []admin_service.ListMyMenuData
_ = json.Unmarshal([]byte(menuCacheData), &menuData)
res.Username = info.Username
res.Nickname = info.Nickname
res.Mobile = info.Mobile
res.Menu = menuData
c.Payload(res)
}
}

View File

@@ -3,9 +3,11 @@ package admin_handler
import (
"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/api/service/admin_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
@@ -28,6 +30,7 @@ type listData struct {
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
IsUsed int `json:"is_used"` // 是否启用 1:是 -1:否
IsOnline int `json:"is_online"` // 是否在线 1:是 -1:否
CreatedAt string `json:"created_at"` // 创建时间
CreatedUser string `json:"created_user"` // 创建人
UpdatedAt string `json:"updated_at"` // 更新时间
@@ -117,6 +120,11 @@ func (h *handler) List() core.HandlerFunc {
h.logger.Info("hashids err", zap.Error(err))
}
isOnline := -1
if h.cache.Exists(configs.RedisKeyPrefixLoginUser + password.GenerateLoginToken(v.Id)) {
isOnline = 1
}
data := listData{
Id: cast.ToInt(v.Id),
HashID: hashId,
@@ -124,6 +132,7 @@ func (h *handler) List() core.HandlerFunc {
Nickname: v.Nickname,
Mobile: v.Mobile,
IsUsed: cast.ToInt(v.IsUsed),
IsOnline: isOnline,
CreatedAt: v.CreatedAt.Format(time_parse.CSTLayout),
CreatedUser: v.CreatedUser,
UpdatedAt: v.UpdatedAt.Format(time_parse.CSTLayout),

View File

@@ -5,14 +5,14 @@ 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/api/service/admin_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/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
type loginRequest struct {
@@ -77,8 +77,60 @@ func (h *handler) Login() core.HandlerFunc {
// 用户信息
adminJsonInfo, _ := json.Marshal(info)
// 记录 Redis 中
err = h.cache.Set(h.adminService.CacheKeyPrefix()+token, string(adminJsonInfo), time.Hour*24, cache.WithTrace(c.Trace()))
// 将用户信息记录 Redis 中
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token, string(adminJsonInfo), time.Hour*24, cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
searchMenuData := new(admin_service.SearchMyMenuData)
searchMenuData.AdminId = info.Id
menu, err := h.adminService.MyMenu(c, searchMenuData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
// 菜单栏信息
menuJsonInfo, _ := json.Marshal(menu)
// 将菜单栏信息记录到 Redis 中
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token+":menu", string(menuJsonInfo), time.Hour*24, cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
searchActionData := new(admin_service.SearchMyActionData)
searchActionData.AdminId = info.Id
action, err := h.adminService.MyAction(c, searchActionData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
// 可访问接口信息
actionJsonInfo, _ := json.Marshal(action)
// 将可访问接口信息记录到 Redis 中
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token+":action", string(actionJsonInfo), time.Hour*24, cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,

View File

@@ -3,14 +3,12 @@ package admin_handler
import (
"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/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
"github.com/spf13/cast"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
type logoutResponse struct {
@@ -31,7 +29,7 @@ func (h *handler) Logout() core.HandlerFunc {
res := new(logoutResponse)
res.Username = c.UserName()
if !h.cache.Del(h.adminService.CacheKeyPrefix()+password.GenerateLoginToken(cast.ToInt32(c.UserID())), cache.WithTrace(c.Trace())) {
if !h.cache.Del(configs.RedisKeyPrefixLoginUser+c.GetHeader(configs.LoginToken), cache.WithTrace(c.Trace())) {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLogOutError,

View File

@@ -0,0 +1,70 @@
package admin_handler
import (
"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/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type offlineRequest struct {
Id string `form:"id"` // 主键ID
}
type offlineResponse struct {
Id int32 `json:"id"` // 主键ID
}
// Offline 下线管理员
// @Summary 下线管理员
// @Description 下线管理员
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param id formData string true "Hashid"
// @Success 200 {object} offlineResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/offline [patch]
func (h *handler) Offline() core.HandlerFunc {
return func(c core.Context) {
req := new(offlineRequest)
res := new(offlineResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
)
return
}
id := int32(ids[0])
b := h.cache.Del(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(id), cache.WithTrace(c.Trace()))
if !b {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminOfflineError,
code.Text(code.AdminOfflineError)),
)
return
}
res.Id = id
c.Payload(res)
}
}

View File

@@ -56,6 +56,11 @@ type Handler interface {
// @Router /api/admin/{id} [delete]
Delete() core.HandlerFunc
// Offline 下线管理员
// @Tags API.admin
// @Router /api/admin/offline [patch]
Offline() core.HandlerFunc
// UpdateUsed 更新管理员为启用/禁用
// @Tags API.admin
// @Router /api/admin/used [patch]

View File

@@ -60,8 +60,8 @@ func (h *handler) Email() core.HandlerFunc {
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()),
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(

View File

@@ -20,6 +20,7 @@ type listData struct {
Link string `json:"link"` // 链接地址
Icon string `json:"icon"` // 图标
IsUsed int32 `json:"is_used"` // 是否启用 1=启用 -1=禁用
Sort int32 `json:"sort"` // 排序
}
type listResponse struct {
@@ -64,6 +65,7 @@ func (h *handler) List() core.HandlerFunc {
Link: v.Link,
Icon: v.Icon,
IsUsed: v.IsUsed,
Sort: v.Sort,
}
res.List[k] = data

View File

@@ -0,0 +1,69 @@
package menu_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 updateSortRequest struct {
Id string `form:"id"` // HashId
Sort int32 `form:"sort"` // 排序
}
type updateSortResponse struct {
Id int32 `json:"id"` // 主键ID
}
// UpdateSort 更新菜单排序
// @Summary 更新菜单排序
// @Description 更新菜单排序
// @Tags API.menu
// @Accept multipart/form-data
// @Produce json
// @Param id formData string true "Hashid"
// @Param sort formData int true "排序"
// @Success 200 {object} updateSortResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu/sort [patch]
func (h *handler) UpdateSort() core.HandlerFunc {
return func(c core.Context) {
req := new(updateSortRequest)
res := new(updateSortResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
)
return
}
id := int32(ids[0])
err = h.menuService.UpdateSort(c, id, req.Sort)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.MenuUpdateError,
code.Text(code.MenuUpdateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}

View File

@@ -21,7 +21,7 @@ type updateUsedResponse struct {
// @Summary 更新菜单为启用/禁用
// @Description 更新菜单为启用/禁用
// @Tags API.menu
// @Accept json
// @Accept multipart/form-data
// @Produce json
// @Param id formData string true "Hashid"
// @Param used formData int true "是否启用 1:是 -1:否"

View File

@@ -36,6 +36,11 @@ type Handler interface {
// @Router /api/menu/used [patch]
UpdateUsed() core.HandlerFunc
// UpdateSort 更新菜单排序
// @Tags API.menu
// @Router /api/menu/sort [patch]
UpdateSort() core.HandlerFunc
// List 菜单列表
// @Tags API.menu
// @Router /api/menu [get]

View File

@@ -377,6 +377,49 @@ func (qb *menuRepoQueryBuilder) OrderByLevel(asc bool) *menuRepoQueryBuilder {
return qb
}
func (qb *menuRepoQueryBuilder) WhereSort(p db_repo.Predicate, value int32) *menuRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "sort", p),
value,
})
return qb
}
func (qb *menuRepoQueryBuilder) WhereSortIn(value []int32) *menuRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "sort", "IN"),
value,
})
return qb
}
func (qb *menuRepoQueryBuilder) WhereSortNotIn(value []int32) *menuRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "sort", "NOT IN"),
value,
})
return qb
}
func (qb *menuRepoQueryBuilder) OrderBySort(asc bool) *menuRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "sort "+order)
return qb
}
func (qb *menuRepoQueryBuilder) WhereIsUsed(p db_repo.Predicate, value int32) *menuRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string

View File

@@ -11,6 +11,7 @@ type Menu struct {
Link string // 链接地址
Icon string // 图标
Level int32 // 菜单类型 1:一级菜单 2:二级菜单
Sort int32 // 排序
IsUsed int32 // 是否启用 1:是 -1:否
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间

View File

@@ -9,9 +9,10 @@
| 4 | link | 链接地址 | varchar(100) | | NO | | |
| 5 | icon | 图标 | varchar(60) | | NO | | |
| 6 | level | 菜单类型 1:一级菜单 2:二级菜单 | tinyint(1) unsigned | | NO | | 1 |
| 7 | is_used | 是否启用 1:是 -1:否 | tinyint(1) | | NO | | 1 |
| 8 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 9 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 10 | created_user | 创建 | varchar(60) | | NO | | |
| 11 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
| 12 | updated_user | 更新 | varchar(60) | | NO | | |
| 7 | sort | 排序 | int(11) unsigned | | NO | | 0 |
| 8 | is_used | 是否启用 1:是 -1:否 | tinyint(1) | | NO | | 1 |
| 9 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 10 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 11 | created_user | 创建人 | varchar(60) | | NO | | |
| 12 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
| 13 | updated_user | 更新人 | varchar(60) | | NO | | |

View File

@@ -1,8 +1,8 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_action_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"
@@ -10,12 +10,8 @@ import (
var _ Service = (*service)(nil)
// 定义缓存前缀
var cacheKeyPrefix = configs.ProjectName() + ":admin:"
type Service interface {
i()
CacheKeyPrefix() (pre string)
Create(ctx core.Context, adminData *CreateAdminData) (id int32, err error)
PageList(ctx core.Context, searchData *SearchData) (listData []*admin_repo.Admin, err error)
@@ -29,6 +25,8 @@ type Service interface {
CreateMenu(ctx core.Context, menuData *CreateMenuData) (err error)
ListMenu(ctx core.Context, searchData *SearchListMenuData) (menuData []ListMenuData, err error)
MyMenu(ctx core.Context, searchData *SearchMyMenuData) (menuData []ListMyMenuData, err error)
MyAction(ctx core.Context, searchData *SearchMyActionData) (actionData []*menu_action_repo.MenuAction, err error)
}
type service struct {
@@ -44,8 +42,3 @@ func New(db db.Repo, cache cache.Repo) Service {
}
func (s *service) i() {}
func (s *service) CacheKeyPrefix() (pre string) {
pre = cacheKeyPrefix
return
}

View File

@@ -1,6 +1,7 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -21,6 +22,6 @@ func (s *service) Delete(ctx core.Context, id int32) (err error) {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -22,7 +22,7 @@ func (s *service) ListMenu(ctx core.Context, searchData *SearchListMenuData) (me
menuQb := menu_repo.NewQueryBuilder()
menuQb.WhereIsDeleted(db_repo.EqualPredicate, -1)
menuListData, err := menuQb.
OrderById(false).
OrderBySort(true).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err

View File

@@ -1,6 +1,7 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -21,6 +22,6 @@ func (s *service) ModifyPassword(ctx core.Context, id int32, newPassword string)
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -0,0 +1,45 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_menu_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_action_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchMyActionData struct {
AdminId int32 `json:"admin_id"` // 管理员ID
}
func (s *service) MyAction(ctx core.Context, searchData *SearchMyActionData) (actionData []*menu_action_repo.MenuAction, err error) {
adminMenuQb := admin_menu_repo.NewQueryBuilder()
if searchData.AdminId != 0 {
adminMenuQb.WhereAdminId(db_repo.EqualPredicate, searchData.AdminId)
}
adminMenuListData, err := adminMenuQb.
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
if len(adminMenuListData) <= 0 {
return
}
var menuIds []int32
for _, v := range adminMenuListData {
menuIds = append(menuIds, v.MenuId)
}
actionQb := menu_action_repo.NewQueryBuilder()
actionQb.WhereIsDeleted(db_repo.EqualPredicate, -1)
actionQb.WhereMenuIdIn(menuIds)
actionData, err = actionQb.QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}

View File

@@ -0,0 +1,69 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_menu_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchMyMenuData struct {
AdminId int32 `json:"admin_id"` // 管理员ID
}
type ListMyMenuData struct {
Id int32 `json:"id"` // ID
Pid int32 `json:"pid"` // 父类ID
Name string `json:"name"` // 菜单名称
Link string `json:"link"` // 链接地址
Icon string `json:"icon"` // 图标
}
func (s *service) MyMenu(ctx core.Context, searchData *SearchMyMenuData) (menuData []ListMyMenuData, err error) {
adminMenuQb := admin_menu_repo.NewQueryBuilder()
if searchData.AdminId != 0 {
adminMenuQb.WhereAdminId(db_repo.EqualPredicate, searchData.AdminId)
}
adminMenuListData, err := adminMenuQb.
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
if len(adminMenuListData) <= 0 {
return
}
menuQb := menu_repo.NewQueryBuilder()
menuQb.WhereIsDeleted(db_repo.EqualPredicate, -1)
menuListData, err := menuQb.
OrderBySort(true).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
if len(menuListData) <= 0 {
return
}
for _, menuAllV := range menuListData {
for _, v := range adminMenuListData {
if menuAllV.Id == v.MenuId {
data := ListMyMenuData{
Id: menuAllV.Id,
Pid: menuAllV.Pid,
Name: menuAllV.Name,
Link: menuAllV.Link,
Icon: menuAllV.Icon,
}
menuData = append(menuData, data)
}
}
}
return
}

View File

@@ -1,6 +1,7 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -21,6 +22,6 @@ func (s *service) ResetPassword(ctx core.Context, id int32) (err error) {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -1,6 +1,7 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -21,6 +22,6 @@ func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error)
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -1,7 +1,6 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -11,9 +10,6 @@ import (
var _ Service = (*service)(nil)
// 定义缓存前缀
var cacheKeyPrefix = configs.ProjectName() + ":authorized:"
type Service interface {
i()

View File

@@ -1,6 +1,7 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
@@ -25,6 +26,6 @@ func (s *service) CreateAPI(ctx core.Context, authorizedAPIData *CreateAuthorize
return 0, err
}
s.cache.Del(cacheKeyPrefix+authorizedAPIData.BusinessKey, cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixSignature+authorizedAPIData.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -1,6 +1,7 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -32,6 +33,6 @@ func (s *service) Delete(ctx core.Context, id int32) (err error) {
return err
}
s.cache.Del(cacheKeyPrefix+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixSignature+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -1,6 +1,7 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -32,6 +33,6 @@ func (s *service) DeleteAPI(ctx core.Context, id int32) (err error) {
return err
}
s.cache.Del(cacheKeyPrefix+authorizedApiInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixSignature+authorizedApiInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
@@ -26,7 +27,7 @@ type cacheApiData struct {
func (s *service) DetailByKey(ctx core.Context, key string) (cacheData *CacheAuthorizedData, err error) {
// 查询缓存
cacheKey := cacheKeyPrefix + key
cacheKey := configs.RedisKeyPrefixSignature + key
value, err := s.cache.Get(cacheKey, cache.WithTrace(ctx.RequestContext().Trace))
cacheData = new(CacheAuthorizedData)

View File

@@ -1,6 +1,7 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -31,6 +32,6 @@ func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error)
return err
}
s.cache.Del(cacheKeyPrefix+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
s.cache.Del(configs.RedisKeyPrefixSignature+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}

View File

@@ -1,7 +1,6 @@
package menu_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_action_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
@@ -11,17 +10,14 @@ import (
var _ Service = (*service)(nil)
// 定义缓存前缀
var cacheKeyPrefix = configs.ProjectName() + ":admin:"
type Service interface {
i()
CacheKeyPrefix() (pre string)
Create(ctx core.Context, menuData *CreateMenuData) (id int32, err error)
Modify(ctx core.Context, id int32, menuData *UpdateMenuData) (err error)
List(ctx core.Context, searchData *SearchData) (listData []*menu_repo.Menu, err error)
UpdateUsed(ctx core.Context, id int32, used int32) (err error)
UpdateSort(ctx core.Context, id int32, sort int32) (err error)
Delete(ctx core.Context, id int32) (err error)
Detail(ctx core.Context, searchOneData *SearchOneData) (info *menu_repo.Menu, err error)
@@ -43,8 +39,3 @@ func New(db db.Repo, cache cache.Repo) Service {
}
func (s *service) i() {}
func (s *service) CacheKeyPrefix() (pre string) {
pre = cacheKeyPrefix
return
}

View File

@@ -20,7 +20,7 @@ func (s *service) List(ctx core.Context, searchData *SearchData) (listData []*me
}
listData, err = qb.
OrderById(false).
OrderBySort(true).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err

View File

@@ -0,0 +1,23 @@
package menu_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/menu_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (s *service) UpdateSort(ctx core.Context, id int32, sort int32) (err error) {
data := map[string]interface{}{
"sort": sort,
"updated_user": ctx.UserName(),
}
qb := menu_repo.NewQueryBuilder()
qb.WhereId(db_repo.EqualPredicate, id)
err = qb.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
return
}

View File

@@ -4,11 +4,11 @@ import (
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/xinliangnote/go-gin-api/pkg/trace"
"github.com/go-redis/redis/v7"
"github.com/pkg/errors"
)
type Option func(*option)

View File

@@ -111,11 +111,11 @@ type Context interface {
// SetHeader 设置 Header
SetHeader(key, value string)
// UserID 获取 JWT 中 UserID
// UserID 获取 UserID
UserID() int64
setUserID(userID int64)
// UserName 获取 JWT 中 UserName
// UserName 获取 UserName
UserName() string
setUserName(userName string)

View File

@@ -252,7 +252,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}
fmt.Println(color.Blue(_UI))
fmt.Println(color.Green(fmt.Sprintf("* [register port %s]", configs.ProjectPort())))
fmt.Println(color.Green(fmt.Sprintf("* [register port %s]", configs.ProjectPort)))
fmt.Println(color.Green(fmt.Sprintf("* [register env %s]", env.Active().Value())))
mux.engine.StaticFS("bootstrap", http.Dir("./assets/bootstrap"))

View File

@@ -7,9 +7,8 @@ import (
"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/errors"
"github.com/xinliangnote/go-gin-api/pkg/token"
"github.com/pkg/errors"
)
func (m *middleware) Jwt(ctx core.Context) (userId int64, userName string, err errno.Error) {

View File

@@ -0,0 +1,77 @@
package middleware
import (
"encoding/json"
"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/api/repository/db_repo/menu_action_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/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/urltable"
)
func (m *middleware) RBAC() core.HandlerFunc {
return func(c core.Context) {
token := c.GetHeader("Token")
if token == "" {
c.AbortWithError(errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Token 参数")),
)
return
}
if !m.cache.Exists(configs.RedisKeyPrefixLoginUser + token) {
c.AbortWithError(errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("请先登录 1")),
)
return
}
if !m.cache.Exists(configs.RedisKeyPrefixLoginUser + token + ":action") {
c.AbortWithError(errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("请先登录 2")),
)
return
}
actionData, err := m.cache.Get(configs.RedisKeyPrefixLoginUser+token+":action", cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
)
return
}
var actions []menu_action_repo.MenuAction
_ = json.Unmarshal([]byte(actionData), &actions)
if len(actions) > 0 {
table := urltable.NewTable()
for _, v := range actions {
_ = table.Append(v.Method + v.Api)
}
if pattern, _ := table.Mapping(c.Method() + c.Path()); pattern == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.RBACError,
code.Text(code.RBACError)).WithErr(errors.New(c.Method() + c.Path() + " 未进行 RBAC 授权")),
)
return
}
}
}
}

View File

@@ -15,9 +15,6 @@ import (
)
func (m *middleware) Resubmit() core.HandlerFunc {
redisKeyPrefix := configs.ProjectName() + ":request-id:"
return func(c core.Context) {
cfg := configs.Get().URLToken
@@ -31,7 +28,7 @@ func (m *middleware) Resubmit() core.HandlerFunc {
return
}
redisKey := redisKeyPrefix + tokenString
redisKey := configs.RedisKeyPrefixRequestID + tokenString
if !m.cache.Exists(redisKey) {
err = m.cache.Set(redisKey, "1", time.Minute*cfg.ExpireDuration)
if err != nil {

View File

@@ -5,13 +5,13 @@ import (
"strings"
"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/errors"
"github.com/xinliangnote/go-gin-api/pkg/signature"
"github.com/xinliangnote/go-gin-api/pkg/urltable"
"github.com/pkg/errors"
)
const ttl = time.Minute * 2 // 签名超时时间 2 分钟
@@ -23,7 +23,7 @@ var whiteListPath = map[string]bool{
func (m *middleware) Signature() core.HandlerFunc {
return func(c core.Context) {
// 签名信息
authorization := c.GetHeader("Authorization")
authorization := c.GetHeader(configs.SignToken)
if authorization == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
@@ -34,7 +34,7 @@ func (m *middleware) Signature() core.HandlerFunc {
}
// 时间信息
date := c.GetHeader("Authorization-Date")
date := c.GetHeader(configs.SignTokenDate)
if date == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,

View File

@@ -4,16 +4,16 @@ import (
"encoding/json"
"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/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err errno.Error) {
token := ctx.GetHeader("Token")
token := ctx.GetHeader(configs.LoginToken)
if token == "" {
err = errno.NewError(
http.StatusUnauthorized,
@@ -23,7 +23,7 @@ func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err
return
}
if !m.cache.Exists(m.adminService.CacheKeyPrefix() + token) {
if !m.cache.Exists(configs.RedisKeyPrefixLoginUser + token) {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
@@ -32,7 +32,7 @@ func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err
return
}
cacheData, cacheErr := m.cache.Get(m.adminService.CacheKeyPrefix()+token, cache.WithTrace(ctx.Trace()))
cacheData, cacheErr := m.cache.Get(configs.RedisKeyPrefixLoginUser+token, cache.WithTrace(ctx.Trace()))
if cacheErr != nil {
err = errno.NewError(
http.StatusUnauthorized,

View File

@@ -31,6 +31,9 @@ type Middleware interface {
// Token 签名验证,对登录用户的验证
Token(ctx core.Context) (userId int64, userName string, err errno.Error)
// RBAC 权限验证
RBAC() core.HandlerFunc
}
type middleware struct {

View File

@@ -9,9 +9,9 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
"github.com/xinliangnote/go-gin-api/internal/router/middleware"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/file"
"github.com/pkg/errors"
"go.uber.org/zap"
)
@@ -39,9 +39,9 @@ func NewHTTPServer(logger *zap.Logger) (*Server, error) {
r := new(resource)
r.logger = logger
openBrowserUri := "http://127.0.0.1" + configs.ProjectPort()
openBrowserUri := "http://127.0.0.1" + configs.ProjectPort
_, ok := file.IsExists(configs.ProjectInstallFile())
_, ok := file.IsExists(configs.ProjectInstallMark)
if !ok { // 未安装
openBrowserUri += "/install"
} else { // 已安装

View File

@@ -30,7 +30,7 @@ func setApiRouter(r *resource) {
}
// 需要签名验证、登录验证、RBAC 权限验证
api := r.mux.Group("/api", core.WrapAuthHandler(r.middles.Token), r.middles.Signature())
api := r.mux.Group("/api", core.WrapAuthHandler(r.middles.Token), r.middles.Signature(), r.middles.RBAC())
{
// authorized
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
@@ -46,6 +46,7 @@ func setApiRouter(r *resource) {
api.POST("/admin", adminHandler.Create())
api.GET("/admin", adminHandler.List())
api.PATCH("/admin/used", adminHandler.UpdateUsed())
api.PATCH("/admin/offline", adminHandler.Offline())
api.PATCH("/admin/reset_password/:id", core.AliasForRecordMetrics("/api/admin/reset_password"), adminHandler.ResetPassword())
api.DELETE("/admin/:id", core.AliasForRecordMetrics("/api/admin"), adminHandler.Delete())
@@ -58,6 +59,7 @@ func setApiRouter(r *resource) {
api.GET("/menu", menuHandler.List())
api.GET("/menu/:id", core.AliasForRecordMetrics("/api/menu"), menuHandler.Detail())
api.PATCH("/menu/used", menuHandler.UpdateUsed())
api.PATCH("/menu/sort", menuHandler.UpdateSort())
api.DELETE("/menu/:id", core.AliasForRecordMetrics("/api/menu"), menuHandler.Delete())
api.POST("/menu_action", menuHandler.CreateAction())
api.GET("/menu_action", menuHandler.ListAction())

View File

@@ -8,6 +8,7 @@ import (
"strings"
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/env"
@@ -48,6 +49,8 @@ type viewResponse struct {
Host string
GoOS string
GoArch string
ProjectVersion string
}
func (h *handler) View() core.HandlerFunc {
@@ -88,6 +91,7 @@ func (h *handler) View() core.HandlerFunc {
obj.Env = env.Active().Value()
obj.GoOS = runtime.GOOS
obj.GoArch = runtime.GOARCH
obj.ProjectVersion = configs.ProjectVersion
c.HTML("dashboard", obj)
}

View File

@@ -260,7 +260,7 @@ func (h *handler) Execute() core.HandlerFunc {
outPutString += "初始化 MySQL 数据表admin_menu 默认数据成功。\n"
// 生成 install 完成标识
f, err := os.Create(configs.ProjectInstallFile())
f, err := os.Create(configs.ProjectInstallMark)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,

View File

@@ -7,6 +7,7 @@ package mysql_table
//`link` varchar(100) NOT NULL DEFAULT '' COMMENT '链接地址',
//`icon` varchar(60) NOT NULL DEFAULT '' COMMENT '图标',
//`level` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '菜单类型 1:一级菜单 2:二级菜单',
//`sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
//`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否',
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -24,6 +25,7 @@ func CreateMenuTableSql() (sql string) {
sql += "`link` varchar(100) NOT NULL DEFAULT '' COMMENT '链接地址',"
sql += "`icon` varchar(60) NOT NULL DEFAULT '' COMMENT '图标',"
sql += "`level` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '菜单类型 1:一级菜单 2:二级菜单',"
sql += "`sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序',"
sql += "`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否',"
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
@@ -37,29 +39,29 @@ func CreateMenuTableSql() (sql string) {
}
func CreateMenuTableDataSql() (sql string) {
sql = "INSERT INTO `menu` (`id`, `pid`, `name`, `link`, `icon`, `level`, `created_user`) VALUES"
sql += "(1, 0, '配置信息', '', 'mdi-settings-box', 1, 'init'),"
sql += "(2, 1, '告警邮箱', '/config/email', '', 2, 'init'),"
sql += "(3, 1, '错误码', '/config/code', '', 2, 'init'),"
sql += "(4, 0, '代码生成器', '', 'mdi-code-not-equal-variant', 1, 'init'),"
sql += "(5, 4, '生成数据表 CURD', '/generator/gorm', '', 2, 'init'),"
sql += "(6, 4, '生成控制器方法', '/generator/handler', '', 2, 'init'),"
sql += "(7, 0, '授权调用方', '', 'mdi-playlist-check', 1, 'init'),"
sql += "(8, 7, '调用方', '/authorized/list', '', 2, 'init'),"
sql += "(9, 7, '使用说明', '/authorized/demo', '', 2, 'init'),"
sql += "(10, 0, '系统管理员', '', 'mdi-account', 1, 'init'),"
sql += "(11, 10, '管理员', '/admin/list', '', 2, 'init'),"
sql += "(12, 10, '菜单管理', '/admin/menu', '', 2, 'init'),"
sql += "(13, 0, '查询小助手', '', 'mdi-database-search', 1, 'init'),"
sql += "(14, 13, '查询缓存', '/tool/cache', '', 2, 'init'),"
sql += "(15, 13, '查询数据', '/tool/data', '', 2, 'init'),"
sql += "(16, 0, '实用工具箱', '', 'mdi-tools', 1, 'init'),"
sql += "(17, 16, 'Hashids', '/tool/hashids', '', 2, 'init'),"
sql += "(18, 16, '调用日志', '/tool/logs', '', 2, 'init'),"
sql += "(19, 16, '接口文档', '/swagger/index.html', '', 2, 'init'),"
sql += "(20, 16, 'GraphQL', '/graphql', '', 2, 'init'),"
sql += "(21, 16, '接口指标', '/metrics', '', 2, 'init'),"
sql += "(22, 16, '服务升级', '/upgrade', '', 2, 'init');"
sql = "INSERT INTO `menu` (`id`, `pid`, `name`, `link`, `icon`, `level`, `sort`, `created_user`) VALUES"
sql += "(1, 0, '配置信息', '', 'mdi-settings-box', 1, 1, 'init'),"
sql += "(2, 1, '告警邮箱', '/config/email', '', 2, 11, 'init'),"
sql += "(3, 1, '错误码', '/config/code', '', 2, 12, 'init'),"
sql += "(4, 0, '代码生成器', '', 'mdi-code-not-equal-variant', 1, 2, 'init'),"
sql += "(5, 4, '生成数据表 CURD', '/generator/gorm', '', 2, 21, 'init'),"
sql += "(6, 4, '生成控制器方法', '/generator/handler', '', 2, 22, 'init'),"
sql += "(7, 0, '授权调用方', '', 'mdi-playlist-check', 1, 3, 'init'),"
sql += "(8, 7, '调用方', '/authorized/list', '', 2, 31, 'init'),"
sql += "(9, 7, '使用说明', '/authorized/demo', '', 2, 32, 'init'),"
sql += "(10, 0, '系统管理员', '', 'mdi-account', 1, 4, 'init'),"
sql += "(11, 10, '管理员', '/admin/list', '', 2, 41, 'init'),"
sql += "(12, 10, '菜单管理', '/admin/menu', '', 2, 42, 'init'),"
sql += "(13, 0, '查询小助手', '', 'mdi-database-search', 1, 5, 'init'),"
sql += "(14, 13, '查询缓存', '/tool/cache', '', 2, 51, 'init'),"
sql += "(15, 13, '查询数据', '/tool/data', '', 2, 52, 'init'),"
sql += "(16, 0, '实用工具箱', '', 'mdi-tools', 1, 6, 'init'),"
sql += "(17, 16, 'Hashids', '/tool/hashids', '', 2, 62, 'init'),"
sql += "(18, 16, '调用日志', '/tool/logs', '', 2, 63, 'init'),"
sql += "(19, 16, '接口文档', '/swagger/index.html', '', 2, 64, 'init'),"
sql += "(20, 16, 'GraphQL', '/graphql', '', 2, 65, 'init'),"
sql += "(21, 16, '接口指标', '/metrics', '', 2, 66, 'init'),"
sql += "(22, 16, '服务升级', '/upgrade', '', 2, 61, 'init');"
return
}

View File

@@ -70,7 +70,9 @@ func CreateMenuActionTableDataSql() (sql string) {
sql += "(34, 12, 'GET', '/api/menu_action', 'init'),"
sql += "(35, 12, 'POST', '/api/menu_action', 'init'),"
sql += "(36, 12, 'DELETE', '/api/menu_action/*', 'init'),"
sql += "(37, 22, 'POST', '/upgrade/execute', 'init');"
sql += "(37, 22, 'POST', '/upgrade/execute', 'init'),"
sql += "(38, 11, 'PATCH', '/api/admin/offline', 'init'),"
sql += "(39, 12, 'PATCH', '/api/menu/sort', 'init');"
return
}

View File

@@ -44,7 +44,7 @@ func (h *handler) LogsView() core.HandlerFunc {
}
return func(c core.Context) {
readLineFromEnd, err := file.NewReadLineFromEnd(configs.ProjectLogFile())
readLineFromEnd, err := file.NewReadLineFromEnd(configs.ProjectLogFile)
if err != nil {
h.logger.Error("NewReadLineFromEnd err", zap.Error(err))
}

View File

@@ -8,7 +8,8 @@ import (
)
type upgradeViewResponse struct {
List []upgradeViewData `json:"list"`
LockFile string `json:"lock_file"`
List []upgradeViewData `json:"list"`
}
type upgradeViewData struct {
@@ -68,6 +69,7 @@ func (h *handler) UpgradeView() core.HandlerFunc {
obj := new(upgradeViewResponse)
obj.List = tableData
obj.LockFile = configs.ProjectInstallMark
c.HTML("upgrade_view", obj)
}
}

View File

@@ -31,9 +31,9 @@ import (
func main() {
// 初始化 logger
loggers, err := logger.NewJSONLogger(
logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName(), env.Active().Value())),
logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName, env.Active().Value())),
logger.WithTimeLayout("2006-01-02 15:04:05"),
logger.WithFileP(configs.ProjectLogFile()),
logger.WithFileP(configs.ProjectLogFile),
)
if err != nil {
panic(err)
@@ -47,7 +47,7 @@ func main() {
}
server := &http.Server{
Addr: configs.ProjectPort(),
Addr: configs.ProjectPort,
Handler: s.Mux,
}