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

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
!function(r,e){"object"==typeof exports?module.exports=exports=e(require("./core")):"function"==typeof define&&define.amd?define(["./core"],e):e(r.CryptoJS)}(this,function(r){var s;return s=r.lib.WordArray,r.enc.Base64={stringify:function(r){var e=r.words,t=r.sigBytes,a=this._map;r.clamp();for(var n=[],o=0;o<t;o+=3)for(var i=(e[o>>>2]>>>24-o%4*8&255)<<16|(e[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|e[o+2>>>2]>>>24-(o+2)%4*8&255,f=0;f<4&&o+.75*f<t;f++)n.push(a.charAt(i>>>6*(3-f)&63));var c=a.charAt(64);if(c)for(;n.length%4;)n.push(c);return n.join("")},parse:function(r){var e=r.length,t=this._map,a=this._reverseMap;if(!a){a=this._reverseMap=[];for(var n=0;n<t.length;n++)a[t.charCodeAt(n)]=n}var o=t.charAt(64);if(o){var i=r.indexOf(o);-1!==i&&(e=i)}return function(r,e,t){for(var a=[],n=0,o=0;o<e;o++)if(o%4){var i=t[r.charCodeAt(o-1)]<<o%4*2,f=t[r.charCodeAt(o)]>>>6-o%4*2,c=i|f;a[n>>>2]|=c<<24-n%4*8,n++}return s.create(a,n)}(r,e,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},r.enc.Base64});
@@ -0,0 +1,18 @@
;(function (root, factory, undef) {
if (typeof exports === "object") {
// CommonJS
module.exports = exports = factory(require("./core"), require("./sha256"), require("./hmac"));
}
else if (typeof define === "function" && define.amd) {
// AMD
define(["./core", "./sha256", "./hmac"], factory);
}
else {
// Global (browser)
factory(root.CryptoJS);
}
}(this, function (CryptoJS) {
return CryptoJS.HmacSHA256;
}));
@@ -0,0 +1,78 @@
// original:https://locutus.io/php/array/ksort/
function ksort(inputArr, sort_flags) {
var tmp_arr = {},
keys = [],
sorter, i, k, that = this,
strictForIn = false,
populateArr = {};
switch (sort_flags) {
case 'SORT_STRING':
// compare items as strings
sorter = function (a, b) {
return that.strnatcmp(a, b);
};
break;
case 'SORT_LOCALE_STRING':
// compare items as strings, original by the current locale (set with i18n_loc_set_default() as of PHP6)
var loc = this.i18n_loc_get_default();
sorter = this.php_js.i18nLocales[loc].sorting;
break;
case 'SORT_NUMERIC':
// compare items numerically
sorter = function (a, b) {
return ((a + 0) - (b + 0));
};
break;
// case 'SORT_REGULAR': // compare items normally (don't change types)
default:
sorter = function (a, b) {
var aFloat = parseFloat(a),
bFloat = parseFloat(b),
aNumeric = aFloat + '' === a,
bNumeric = bFloat + '' === b;
if (aNumeric && bNumeric) {
return aFloat > bFloat ? 1 : aFloat < bFloat ? -1 : 0;
} else if (aNumeric && !bNumeric) {
return 1;
} else if (!aNumeric && bNumeric) {
return -1;
}
return a > b ? 1 : a < b ? -1 : 0;
};
break;
}
// Make a list of key names
for (k in inputArr) {
if (inputArr.hasOwnProperty(k)) {
keys.push(k);
}
}
keys.sort(sorter);
// BEGIN REDUNDANT
this.php_js = this.php_js || {};
this.php_js.ini = this.php_js.ini || {};
// END REDUNDANT
strictForIn = this.php_js.ini['phpjs.strictForIn'] && this.php_js.ini['phpjs.strictForIn'].local_value && this.php_js
.ini['phpjs.strictForIn'].local_value !== 'off';
populateArr = strictForIn ? inputArr : populateArr;
// Rebuild array with sorted key names
for (i = 0; i < keys.length; i++) {
k = keys[i];
tmp_arr[k] = inputArr[k];
if (strictForIn) {
delete inputArr[k];
}
}
for (i in tmp_arr) {
if (tmp_arr.hasOwnProperty(i)) {
populateArr[i] = tmp_arr[i];
}
}
return strictForIn || populateArr;
}
+2
View File
@@ -0,0 +1,2 @@
!function(n){"use strict";function d(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function f(n,t,r,e,o,u){return d((c=d(d(t,n),d(e,u)))<<(f=o)|c>>>32-f,r);var c,f}function l(n,t,r,e,o,u,c){return f(t&r|~t&e,n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f(t&e|r&~e,n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u;n[t>>5]|=128<<t%32,n[14+(t+64>>>9<<4)]=t;for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h<n.length;h+=16)c=l(r=c,e=f,o=i,u=a,n[h],7,-680876936),a=l(a,c,f,i,n[h+1],12,-389564586),i=l(i,a,c,f,n[h+2],17,606105819),f=l(f,i,a,c,n[h+3],22,-1044525330),c=l(c,f,i,a,n[h+4],7,-176418897),a=l(a,c,f,i,n[h+5],12,1200080426),i=l(i,a,c,f,n[h+6],17,-1473231341),f=l(f,i,a,c,n[h+7],22,-45705983),c=l(c,f,i,a,n[h+8],7,1770035416),a=l(a,c,f,i,n[h+9],12,-1958414417),i=l(i,a,c,f,n[h+10],17,-42063),f=l(f,i,a,c,n[h+11],22,-1990404162),c=l(c,f,i,a,n[h+12],7,1804603682),a=l(a,c,f,i,n[h+13],12,-40341101),i=l(i,a,c,f,n[h+14],17,-1502002290),c=v(c,f=l(f,i,a,c,n[h+15],22,1236535329),i,a,n[h+1],5,-165796510),a=v(a,c,f,i,n[h+6],9,-1069501632),i=v(i,a,c,f,n[h+11],14,643717713),f=v(f,i,a,c,n[h],20,-373897302),c=v(c,f,i,a,n[h+5],5,-701558691),a=v(a,c,f,i,n[h+10],9,38016083),i=v(i,a,c,f,n[h+15],14,-660478335),f=v(f,i,a,c,n[h+4],20,-405537848),c=v(c,f,i,a,n[h+9],5,568446438),a=v(a,c,f,i,n[h+14],9,-1019803690),i=v(i,a,c,f,n[h+3],14,-187363961),f=v(f,i,a,c,n[h+8],20,1163531501),c=v(c,f,i,a,n[h+13],5,-1444681467),a=v(a,c,f,i,n[h+2],9,-51403784),i=v(i,a,c,f,n[h+7],14,1735328473),c=g(c,f=v(f,i,a,c,n[h+12],20,-1926607734),i,a,n[h+5],4,-378558),a=g(a,c,f,i,n[h+8],11,-2022574463),i=g(i,a,c,f,n[h+11],16,1839030562),f=g(f,i,a,c,n[h+14],23,-35309556),c=g(c,f,i,a,n[h+1],4,-1530992060),a=g(a,c,f,i,n[h+4],11,1272893353),i=g(i,a,c,f,n[h+7],16,-155497632),f=g(f,i,a,c,n[h+10],23,-1094730640),c=g(c,f,i,a,n[h+13],4,681279174),a=g(a,c,f,i,n[h],11,-358537222),i=g(i,a,c,f,n[h+3],16,-722521979),f=g(f,i,a,c,n[h+6],23,76029189),c=g(c,f,i,a,n[h+9],4,-640364487),a=g(a,c,f,i,n[h+12],11,-421815835),i=g(i,a,c,f,n[h+15],16,530742520),c=m(c,f=g(f,i,a,c,n[h+2],23,-995338651),i,a,n[h],6,-198630844),a=m(a,c,f,i,n[h+7],10,1126891415),i=m(i,a,c,f,n[h+14],15,-1416354905),f=m(f,i,a,c,n[h+5],21,-57434055),c=m(c,f,i,a,n[h+12],6,1700485571),a=m(a,c,f,i,n[h+3],10,-1894986606),i=m(i,a,c,f,n[h+10],15,-1051523),f=m(f,i,a,c,n[h+1],21,-2054922799),c=m(c,f,i,a,n[h+8],6,1873313359),a=m(a,c,f,i,n[h+15],10,-30611744),i=m(i,a,c,f,n[h+6],15,-1560198380),f=m(f,i,a,c,n[h+13],21,1309151649),c=m(c,f,i,a,n[h+4],6,-145523070),a=m(a,c,f,i,n[h+11],10,-1120210379),i=m(i,a,c,f,n[h+2],15,718787259),f=m(f,i,a,c,n[h+9],21,-343485551),c=d(c,r),f=d(f,e),i=d(i,o),a=d(a,u);return[c,f,i,a]}function a(n){for(var t="",r=32*n.length,e=0;e<r;e+=8)t+=String.fromCharCode(n[e>>5]>>>e%32&255);return t}function h(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e<t.length;e+=1)t[e]=0;for(var r=8*n.length,e=0;e<r;e+=8)t[e>>5]|=(255&n.charCodeAt(e/8))<<e%32;return t}function e(n){for(var t,r="0123456789abcdef",e="",o=0;o<n.length;o+=1)t=n.charCodeAt(o),e+=r.charAt(t>>>4&15)+r.charAt(15&t);return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return a(i(h(t=r(n)),8*t.length));var t}function u(n,t){return function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,16<o.length&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}(r(n),r(t))}function t(n,t,r){return t?r?u(t,n):e(u(t,n)):r?o(n):e(o(n))}"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:n.md5=t}(this);
//# sourceMappingURL=md5.min.js.map
@@ -0,0 +1,589 @@
/* ==========================================================
* bootstrap-maxlength.js v1.9.0
*
* Copyright (c) 2013-2020 Maurizio Napoleoni;
*
* Licensed under the terms of the MIT license.
* See: https://github.com/mimo84/bootstrap-maxlength/blob/master/LICENSE
* ========================================================== */
/*global jQuery*/
(function ($) {
'use strict';
/**
* We need an event when the elements are destroyed
* because if an input is removed, we have to remove the
* maxlength object associated (if any).
* From:
* http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
*/
if (!$.event.special.destroyed) {
$.event.special.destroyed = {
remove: function (o) {
if (o.handler) {
o.handler();
}
}
};
}
$.fn.extend({
maxlength: function (options, callback) {
var documentBody = $('body'),
defaults = {
showOnReady: false, // true to always show when indicator is ready
alwaysShow: true, // if true the indicator it's always shown.
threshold: 0, // Represents how many chars left are needed to show up the counter
warningClass: 'small form-text text-muted',
limitReachedClass: 'small form-text text-danger',
separator: ' / ',
preText: '',
postText: '',
showMaxLength: true,
placement: 'bottom-right-inside',
message: null, // an alternative way to provide the message text
showCharsTyped: true, // show the number of characters typed and not the number of characters remaining
validate: false, // if the browser doesn't support the maxlength attribute, attempt to type more than the indicated chars, will be prevented.
utf8: false, // counts using bytesize rather than length. eg: '£' is counted as 2 characters.
appendToParent: false, // append the indicator to the input field's parent instead of body
twoCharLinebreak: true, // count linebreak as 2 characters to match IE/Chrome textarea validation. As well as DB storage.
customMaxAttribute: null, // null = use maxlength attribute and browser functionality, string = use specified attribute instead.
allowOverMax: false, // Form submit validation is handled on your own. when maxlength has been exceeded 'overmax' class added to element
zIndex: 1099
};
if ($.isFunction(options) && !callback) {
callback = options;
options = {};
}
options = $.extend(defaults, options);
/**
* Return the byte count of the specified character in UTF8 encoding.
* Note: This won't cover UTF-8 characters that are 4 bytes long.
*
* @param input
* @return {number}
*/
function utf8CharByteCount(character) {
var c = character.charCodeAt();
// Not c then 0, else c < 128 then 1, else c < 2048 then 2, else 3
return !c ? 0 : c < 128 ? 1 : c < 2048 ? 2 : 3;
}
/**
* Return the length of the specified input in UTF8 encoding.
*
* @param input
* @return {number}
*/
function utf8Length(string) {
return string.split("")
.map(utf8CharByteCount)
// Prevent reduce from throwing an error if the string is empty.
.concat(0)
.reduce(function (sum, val) {
return sum + val;
});
}
/**
* Return the length of the specified input.
*
* @param input
* @return {number}
*/
function inputLength(input) {
var text = input.val();
if (options.twoCharLinebreak) {
// Count all line breaks as 2 characters
text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');
} else {
// Remove all double-character (\r\n) linebreaks, so they're counted only once.
text = text.replace(/(?:\r\n|\r|\n)/g, '\n');
}
var currentLength = 0;
if (options.utf8) {
currentLength = utf8Length(text);
} else {
currentLength = text.length;
}
// Remove "C:\fakepath\" from counter when using file input
// Fix https://github.com/mimo84/bootstrap-maxlength/issues/146
if (input.prop("type") === "file" && input.val() !== "") {
currentLength -= 12;
}
return currentLength;
}
/**
* Truncate the text of the specified input.
*
* @param input
* @param limit
*/
function truncateChars(input, maxlength) {
var text = input.val();
if (options.twoCharLinebreak) {
text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');
if (text[text.length - 1] === '\n') {
maxlength -= text.length % 2;
}
}
if (options.utf8) {
var indexedSize = text.split("").map(utf8CharByteCount);
for (
var removedBytes = 0,
bytesPastMax = utf8Length(text) - maxlength; removedBytes < bytesPastMax; removedBytes += indexedSize.pop()
);
maxlength -= (maxlength - indexedSize.length);
}
input.val(text.substr(0, maxlength));
}
/**
* Return true if the indicator should be showing up.
*
* @param input
* @param threshold
* @param maxlength
* @return {number}
*/
function charsLeftThreshold(input, threshold, maxlength) {
var output = true;
if (!options.alwaysShow && (maxlength - inputLength(input) > threshold)) {
output = false;
}
return output;
}
/**
* Returns how many chars are left to complete the fill up of the form.
*
* @param input
* @param maxlength
* @return {number}
*/
function remainingChars(input, maxlength) {
var length = maxlength - inputLength(input);
return length;
}
/**
* When called displays the indicator.
*
* @param indicator
*/
function showRemaining(currentInput, indicator) {
indicator.css({
display: 'block'
});
currentInput.trigger('maxlength.shown');
}
/**
* When called shows the indicator.
*
* @param indicator
*/
function hideRemaining(currentInput, indicator) {
if (options.alwaysShow) {
return;
}
indicator.css({
display: 'none'
});
currentInput.trigger('maxlength.hidden');
}
/**
* This function updates the value in the indicator
*
* @param maxLengthThisInput
* @param typedChars
* @return String
*/
function updateMaxLengthHTML(currentInputText, maxLengthThisInput, typedChars) {
var output = '';
if (options.message) {
if (typeof options.message === 'function') {
output = options.message(currentInputText, maxLengthThisInput);
} else {
output = options.message.replace('%charsTyped%', typedChars)
.replace('%charsRemaining%', maxLengthThisInput - typedChars)
.replace('%charsTotal%', maxLengthThisInput);
}
} else {
if (options.preText) {
output += options.preText;
}
if (!options.showCharsTyped) {
output += maxLengthThisInput - typedChars;
} else {
output += typedChars;
}
if (options.showMaxLength) {
output += options.separator + maxLengthThisInput;
}
if (options.postText) {
output += options.postText;
}
}
return output;
}
/**
* This function updates the value of the counter in the indicator.
* Wants as parameters: the number of remaining chars, the element currently managed,
* the maxLength for the current input and the indicator generated for it.
*
* @param remaining
* @param currentInput
* @param maxLengthCurrentInput
* @param maxLengthIndicator
*/
function manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator) {
if (maxLengthIndicator) {
maxLengthIndicator.html(updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, (maxLengthCurrentInput - remaining)));
if (remaining > 0) {
if (charsLeftThreshold(currentInput, options.threshold, maxLengthCurrentInput)) {
showRemaining(currentInput, maxLengthIndicator.removeClass(options.limitReachedClass).addClass(options.warningClass));
} else {
hideRemaining(currentInput, maxLengthIndicator);
}
} else {
showRemaining(currentInput, maxLengthIndicator.removeClass(options.warningClass).addClass(options.limitReachedClass));
}
}
if (options.customMaxAttribute) {
// class to use for form validation on custom maxlength attribute
if (remaining < 0) {
currentInput.addClass('overmax');
} else {
currentInput.removeClass('overmax');
}
}
}
/**
* This function returns an object containing all the
* informations about the position of the current input
*
* @param currentInput
* @return object {bottom height left right top width}
*
*/
function getPosition(currentInput) {
var el = currentInput[0];
return $.extend({}, (typeof el.getBoundingClientRect === 'function') ? el.getBoundingClientRect() : {
width: el.offsetWidth,
height: el.offsetHeight
}, currentInput.offset());
}
/**
* This function places the maxLengthIndicator based on placement config object.
*
* @param {object} placement
* @param {$} maxLengthIndicator
* @return null
*
*/
function placeWithCSS(placement, maxLengthIndicator) {
if (!placement || !maxLengthIndicator) {
return;
}
var POSITION_KEYS = [
'top',
'bottom',
'left',
'right',
'position'
];
var cssPos = {};
// filter css properties to position
$.each(POSITION_KEYS, function (i, key) {
var val = options.placement[key];
if (typeof val !== 'undefined') {
cssPos[key] = val;
}
});
maxLengthIndicator.css(cssPos);
return;
}
/**
* This function places the maxLengthIndicator at the
* top / bottom / left / right of the currentInput
*
* @param currentInput
* @param maxLengthIndicator
* @return null
*
*/
function place(currentInput, maxLengthIndicator) {
var pos = getPosition(currentInput);
// Supports custom placement handler
if ($.type(options.placement) === 'function') {
options.placement(currentInput, maxLengthIndicator, pos);
return;
}
// Supports custom placement via css positional properties
if ($.isPlainObject(options.placement)) {
placeWithCSS(options.placement, maxLengthIndicator);
return;
}
var inputOuter = currentInput.outerWidth(),
outerWidth = maxLengthIndicator.outerWidth(),
actualWidth = maxLengthIndicator.width(),
actualHeight = maxLengthIndicator.height();
// get the right position if the indicator is appended to the input's parent
if (options.appendToParent) {
pos.top -= currentInput.parent().offset().top;
pos.left -= currentInput.parent().offset().left;
}
switch (options.placement) {
case 'bottom':
maxLengthIndicator.css({
top: pos.top + pos.height,
left: pos.left + pos.width / 2 - actualWidth / 2
});
break;
case 'top':
maxLengthIndicator.css({
top: pos.top - actualHeight,
left: pos.left + pos.width / 2 - actualWidth / 2
});
break;
case 'left':
maxLengthIndicator.css({
top: pos.top + pos.height / 2 - actualHeight / 2,
left: pos.left - actualWidth
});
break;
case 'right':
maxLengthIndicator.css({
top: pos.top + pos.height / 2 - actualHeight / 2,
left: pos.left + pos.width
});
break;
case 'bottom-right':
maxLengthIndicator.css({
top: pos.top + pos.height,
left: pos.left + pos.width
});
break;
case 'top-right':
maxLengthIndicator.css({
top: pos.top - actualHeight,
left: pos.left + inputOuter
});
break;
case 'top-left':
maxLengthIndicator.css({
top: pos.top - actualHeight,
left: pos.left - outerWidth
});
break;
case 'bottom-left':
maxLengthIndicator.css({
top: pos.top + currentInput.outerHeight(),
left: pos.left - outerWidth
});
break;
case 'centered-right':
maxLengthIndicator.css({
top: pos.top + (actualHeight / 2),
left: pos.left + inputOuter - outerWidth - 3
});
break;
// Some more options for placements
case 'bottom-right-inside':
maxLengthIndicator.css({
top: pos.top + pos.height,
left: pos.left + pos.width - outerWidth
});
break;
case 'top-right-inside':
maxLengthIndicator.css({
top: pos.top - actualHeight,
left: pos.left + inputOuter - outerWidth
});
break;
case 'top-left-inside':
maxLengthIndicator.css({
top: pos.top - actualHeight,
left: pos.left
});
break;
case 'bottom-left-inside':
maxLengthIndicator.css({
top: pos.top + currentInput.outerHeight(),
left: pos.left
});
break;
}
}
/**
* This function returns true if the indicator position needs to
* be recalculated when the currentInput changes
*
* @return {boolean}
*
*/
function isPlacementMutable() {
return options.placement === 'bottom-right-inside' || options.placement === 'top-right-inside' || typeof options.placement === 'function' || (options.message && typeof options.message === 'function');
}
/**
* This function retrieves the maximum length of currentInput
*
* @param currentInput
* @return {number}
*
*/
function getMaxLength(currentInput) {
var max = currentInput.attr('maxlength') || options.customMaxAttribute;
if (options.customMaxAttribute && !options.allowOverMax) {
var custom = currentInput.attr(options.customMaxAttribute);
if (!max || custom < max) {
max = custom;
}
}
if (!max) {
max = currentInput.attr('size');
}
return max;
}
return this.each(function () {
var currentInput = $(this),
maxLengthCurrentInput,
maxLengthIndicator;
$(window).resize(function () {
if (maxLengthIndicator) {
place(currentInput, maxLengthIndicator);
}
});
function firstInit() {
var maxlengthContent = updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, '0');
maxLengthCurrentInput = getMaxLength(currentInput);
if (!maxLengthIndicator) {
maxLengthIndicator = $('<span class="bootstrap-maxlength"></span>').css({
display: 'none',
position: 'absolute',
whiteSpace: 'nowrap',
zIndex: options.zIndex
}).html(maxlengthContent);
}
// We need to detect resizes if we are dealing with a textarea:
if (currentInput.is('textarea')) {
currentInput.data('maxlenghtsizex', currentInput.outerWidth());
currentInput.data('maxlenghtsizey', currentInput.outerHeight());
currentInput.mouseup(function () {
if (currentInput.outerWidth() !== currentInput.data('maxlenghtsizex') || currentInput.outerHeight() !== currentInput.data('maxlenghtsizey')) {
place(currentInput, maxLengthIndicator);
}
currentInput.data('maxlenghtsizex', currentInput.outerWidth());
currentInput.data('maxlenghtsizey', currentInput.outerHeight());
});
}
if (options.appendToParent) {
currentInput.parent().append(maxLengthIndicator);
currentInput.parent().css('position', 'relative');
} else {
documentBody.append(maxLengthIndicator);
}
var remaining = remainingChars(currentInput, getMaxLength(currentInput));
manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
place(currentInput, maxLengthIndicator);
}
if (options.showOnReady) {
currentInput.ready(function () {
firstInit();
});
} else {
currentInput.focus(function () {
firstInit();
});
}
currentInput.on('maxlength.reposition', function () {
place(currentInput, maxLengthIndicator);
});
currentInput.on('destroyed', function () {
if (maxLengthIndicator) {
maxLengthIndicator.remove();
}
});
currentInput.on('blur', function () {
if (maxLengthIndicator && !options.showOnReady) {
maxLengthIndicator.remove();
}
});
currentInput.on('input', function () {
var maxlength = getMaxLength(currentInput),
remaining = remainingChars(currentInput, maxlength),
output = true;
if (options.validate && remaining < 0) {
truncateChars(currentInput, maxlength);
output = false;
} else {
manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
}
if (isPlacementMutable()) {
place(currentInput, maxLengthIndicator);
}
return output;
});
});
}
});
}(jQuery));
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,114 @@
.mt-wrapper{
position: relative;
z-index: 1;
}
.mt-nav-bar {
width : 100%;
z-index: 200;
position: absolute;
display: flex;
}
.mt-nav-bar .mt-nav{
background-color: #fff;
}
.mt-nav-panel{
overflow: hidden;
}
.mt-nav-panel ul{
width: 10000px;
}
.mt-nav-panel ul li {
position: relative;
}
.mt-tab-content{
height: 100%;
}
.mt-close-tab {
position: absolute;
font-size: 10px;
width: 16px;
height: 16px;
top: 18px;
right: 10px;
color: #c2c2c2;
cursor: pointer;
display: none;
line-height: 16px;
text-align: center;
vertical-align: 2px;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
transition: all .3s cubic-bezier(.645,.045,.355,1);
-webkit-transform-origin: 100% 50%;
transform-origin: 100% 50%;
}
.mt-close-tab:before {
-webkit-transform: scale(.8);
transform: scale(.8);
display: inline-block;
/*vertical-align: -1px;*/
}
li:hover .mt-close-tab {
display: inline;
}
.mt-hidden-list .mt-close-tab {
display: none !important;
}
.mt-nav-bar a {
cursor: pointer !important;
max-height: 48px;
padding-top: 14px;
padding-bottom: 13px;
}
@media (max-width: 767px){
.mt-tab-content{
/*padding-top: 0 !important;*/
}
}
.mt-tab-content{
-webkit-overflow-scrolling:touch;
overflow:auto;
}
.mt-dragging-tab{
left: auto;
position: absolute !important;
z-index: 9999 !important;
}
.mt-dragging-tab > a{
background: #FBFDFD !important;
}
/*新增*/
.mt-nav-bar .mt-nav .nav-tabs {
margin-bottom: 0px;
border-color: #eceeef;
}
.mt-close-tab:hover {
color: #f96868;
}
.mt-dropdown .caret {
position: absolute;
top: 22px;
}
.mt-dropdown .dropdown-menu {
margin-top: 0px;
}
#contextify-menu {
min-width: 80px!important;
}
.mt-close-tab:hover {
background-color: #f96868;
color: #fff;
}
.mt-nav .nav-tabs a:not([data-type="main"]) {
padding-right: 40px;
}
.mt-nav-tools-left li a,
.mt-nav-tools-right li a {
padding-right: 15px!important;
}
.mt-nav-bar .mt-nav .nav-tabs .mt-dragging {
margin-bottom: -1px;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,104 @@
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/ksort.js"></script>');
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/crypto-js.min.js"></script>');
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/hmac-sha256.js"></script>');
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/enc-base64.min.js"></script>');
document.write('<script type="text/javascript" src="../../bootstrap/js/jquery.cookie.min.js"></script>');
function GenerateAuthorization(path, method, params) {
let key = "admin";
let secret = "12878dd962115106db6d";
let date = new Date();
let datetime = date.getFullYear() + "-" // "年"
+ ((date.getMonth() + 1) > 10 ? (date.getMonth() + 1) : "0" + (date.getMonth() + 1)) + "-" // "月"
+ (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " " // "日"
+ (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":" // "小时"
+ (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":" // "分钟"
+ (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds()); // "秒"
let sortParamsEncode = decodeURIComponent(jQuery.param(ksort(params)));
let encryptStr = path + "|" + method.toUpperCase() + "|" + sortParamsEncode + "|" + datetime;
let digest = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(encryptStr, secret));
return {authorization: key + " " + digest, date: datetime};
}
function IsJson(str) {
if (typeof str == 'string') {
try {
let obj = JSON.parse(str);
if (typeof obj == 'object' && obj) {
return true;
} else {
return false;
}
} catch (e) {
console.log('error' + str + '!!!' + e);
return false;
}
}
console.log('It is not a string!')
}
function AjaxError(response) {
let errCode = response.status;
let errMsg = response.responseText;
if (errCode === 401) { // 跳转到登录页
// 关闭当前界面
parent.window.close();
window.open("/login");
return;
}
if (IsJson(response.responseText)) {
const errInfo = JSON.parse(response.responseText);
errCode = errInfo.code;
errMsg = errInfo.message;
}
$.alert({
title: '错误提示',
icon: 'mdi mdi-alert',
type: 'red',
content: '错误码:' + errCode + '<br/>' + '错误信息:' + errMsg,
});
}
function AjaxForm(method, url, params, beforeSendFunction, successFunction, errorFunction) {
let authorizationData = GenerateAuthorization(url, method, params);
$.ajax({
url: url,
type: method,
data: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Authorization': authorizationData.authorization,
'Authorization-Date': authorizationData.date,
'Token': $.cookie("_login_token_"),
},
beforeSend: beforeSendFunction,
success: successFunction,
error: errorFunction,
});
}
function AjaxPostJson(url, params, beforeSendFunction, successFunction, errorFunction) {
let authorizationData = GenerateAuthorization(url, "POST", params);
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(params),
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': authorizationData.authorization,
'Authorization-Date': authorizationData.date,
'Token': $.cookie("_login_token_"),
},
beforeSend: beforeSendFunction,
success: successFunction,
error: errorFunction,
});
}
+139
View File
@@ -0,0 +1,139 @@
;jQuery( function() {
// 停止
$("body").on('click','[data-stopPropagation]',function (e) {
e.stopPropagation();
});
// 滚动条
if($('.lyear-scroll')[0]) {
$('.lyear-scroll').each(function(){
new PerfectScrollbar(this, {
swipeEasing: false,
suppressScrollX: true
});
});
}
// 侧边栏
$(document).on('click', '.lyear-aside-toggler', function() {
$('.lyear-layout-sidebar').toggleClass('lyear-aside-open');
$("body").toggleClass('lyear-layout-sidebar-close');
if ($('.lyear-mask-modal').length == 0) {
$('<div class="lyear-mask-modal"></div>').prependTo('body');
} else {
$( '.lyear-mask-modal' ).remove();
}
});
// 遮罩层
$(document).on('click', '.lyear-mask-modal', function() {
$( this ).remove();
$('.lyear-layout-sidebar').toggleClass('lyear-aside-open');
$('body').toggleClass('lyear-layout-sidebar-close');
});
// 侧边栏导航
$(document).on('click', '.nav-item-has-subnav > a', function() {
$subnavToggle = jQuery( this );
$navHasSubnav = $subnavToggle.parent();
$topHasSubNav = $subnavToggle.parents('.nav-item-has-subnav').last();
$subnav = $navHasSubnav.find('.nav-subnav').first();
$viSubHeight = $navHasSubnav.siblings().find('.nav-subnav:visible').outerHeight();
$scrollBox = $('.lyear-layout-sidebar-info');
$navHasSubnav.siblings().find('.nav-subnav:visible').slideUp(500).parent().removeClass('open');
$subnav.slideToggle( 300, function() {
$navHasSubnav.toggleClass( 'open' );
// 新增滚动条处理
var scrollHeight = 0;
pervTotal = $topHasSubNav.prevAll().length,
boxHeight = $scrollBox.outerHeight(),
innerHeight = $('.sidebar-main').outerHeight(),
thisScroll = $scrollBox.scrollTop(),
thisSubHeight = $(this).outerHeight(),
footHeight = 121;
if (footHeight + innerHeight - boxHeight >= (pervTotal * 48)) {
scrollHeight = pervTotal * 48;
}
if ($subnavToggle.parents('.nav-item-has-subnav').length == 1) {
$scrollBox.animate({scrollTop: scrollHeight}, 300);
} else {
// 子菜单操作
if (typeof($viSubHeight) != 'undefined' && $viSubHeight != null) {
scrollHeight = thisScroll + thisSubHeight - $viSubHeight;
$scrollBox.animate({scrollTop: scrollHeight}, 300);
} else {
if ((thisScroll + boxHeight - $scrollBox[0].scrollHeight) == 0) {
scrollHeight = thisScroll - thisSubHeight;
$scrollBox.animate({scrollTop: scrollHeight}, 300);
}
}
}
});
});
// 读取cookie中的主题设置
var the_logo_bg = $.cookie('the_logo_bg'),
the_header_bg = $.cookie('the_header_bg'),
the_sidebar_bg = $.cookie('the_sidebar_bg');
if (the_logo_bg) $('body').attr('data-logobg', the_logo_bg);
if (the_header_bg) $('body').attr('data-headerbg', the_header_bg);
if (the_sidebar_bg) $('body').attr('data-sidebarbg', the_sidebar_bg);
// 处理主题配色下拉选中
$(".dropdown-skin :radio").each(function(){
var $this = $(this),
radioName = $this.attr('name');
switch (radioName) {
case 'logo_bg':
$this.val() == the_logo_bg && $this.prop("checked", true);
break;
case 'header_bg':
$this.val() == the_header_bg && $this.prop("checked", true);
break;
case 'sidebar_bg':
$this.val() == the_sidebar_bg && $this.prop("checked", true);
}
});
// 设置主题配色
setTheme = function(input_name, data_name) {
$("input[name='"+input_name+"']").click(function(){
$('body').attr(data_name, $(this).val());
$.cookie('the_'+input_name, $(this).val());
});
}
setTheme('sidebar_bg', 'data-sidebarbg');
setTheme('logo_bg', 'data-logobg');
setTheme('header_bg', 'data-headerbg');
// 选项卡
$('#iframe-content').multitabs({
iframe : true,
refresh : 'nav', // iframe中页面是否刷新,'no''从不刷新''nav''点击菜单刷新''all''菜单和tab点击都刷新'
nav: {
backgroundColor: '#ffffff',
maxTabs : 35, // 选项卡最大值
},
init : [{
type : 'main',
title : $.cookie('_nav_title_') ? $.cookie('_nav_title_') : '仪表盘',
url : $.cookie('_nav_url_') ? $.cookie('_nav_url_') : '/dashboard',
}]
});
$(document).on('click', '.nav-item .multitabs', function() {
$('.nav-item').removeClass('active');
$('.nav-subnav li').removeClass('active');
$(this).parent('li').addClass('active');
$(this).parents('.nav-item-has-subnav').addClass('open').first().addClass('active');
var date = new Date();
date.setTime(date.getTime() + 24 * 60 * 60 * 1000); // 24 * 60 * 60 * 1000 表示 24 小时
$.cookie('_nav_url_', $(this).attr('href'), {expires: date});
$.cookie('_nav_title_', $(this).text(), {expires: date});
});
});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
/*! jquery.cookie v1.4.1 | MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});
File diff suppressed because one or more lines are too long
+292
View File
@@ -0,0 +1,292 @@
/**
* pagination.js 1.5.1
* A jQuery plugin to provide simple yet fully customisable pagination.
* @version 1.5.1
* @author mss
* @url https://github.com/Maxiaoxiang/jQuery-plugins
*
* @调用方法
* $(selector).pagination(option, callback);
* -此处callback是初始化调用,option里的callback是点击页码后调用
*
* -- example --
* $(selector).pagination({
* ... // 配置参数
* callback: function(api) {
* console.log('点击页码调用该回调'); //切换页码时执行一次回调
* }
* }, function(){
* console.log('初始化'); //插件初始化时调用该方法,比如请求第一次接口来初始化分页配置
* });
*/
;
(function (factory) {
if (typeof define === "function" && (define.amd || define.cmd) && !jQuery) {
// AMD或CMD
define(["jquery"], factory);
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = function (root, jQuery) {
if (jQuery === undefined) {
if (typeof window !== 'undefined') {
jQuery = require('jquery');
} else {
jQuery = require('jquery')(root);
}
}
factory(jQuery);
return jQuery;
};
} else {
//Browser globals
factory(jQuery);
}
}(function ($) {
//配置参数
var defaults = {
totalData: 0, //数据总条数
showData: 0, //每页显示的条数
pageCount: 9, //总页数,默认为9
current: 1, //当前第几页
prevCls: 'prev', //上一页class
nextCls: 'next', //下一页class
prevContent: '<', //上一页内容
nextContent: '>', //下一页内容
activeCls: 'active', //当前页选中状态
coping: false, //首页和尾页
isHide: false, //当前页数为0页或者1页时不显示分页
homePage: '', //首页节点内容
endPage: '', //尾页节点内容
keepShowPN: false, //是否一直显示上一页下一页
mode: 'unfixed', //分页模式,unfixed:不固定页码数量,fixed:固定页码数量
count: 4, //mode为unfixed时显示当前选中页前后页数,mode为fixed显示页码总数
jump: false, //跳转到指定页数
jumpIptCls: 'jump-ipt', //文本框内容
jumpBtnCls: 'jump-btn', //跳转按钮
jumpBtn: '跳转', //跳转按钮文本
callback: function () {} //回调
};
var Pagination = function (element, options) {
//全局变量
var opts = options, //配置
current, //当前页
$document = $(document),
$obj = $(element); //容器
/**
* 设置总页数
* @param {int} page 页码
* @return opts.pageCount 总页数配置
*/
this.setPageCount = function (page) {
return opts.pageCount = page;
};
/**
* 获取总页数
* 如果配置了总条数和每页显示条数,将会自动计算总页数并略过总页数配置,反之
* @return {int} 总页数
*/
this.getPageCount = function () {
return opts.totalData && opts.showData ? Math.ceil(parseInt(opts.totalData) / opts.showData) : opts.pageCount;
};
/**
* 获取当前页
* @return {int} 当前页码
*/
this.getCurrent = function () {
return current;
};
/**
* 填充数据
* @param {int} 页码
*/
this.filling = function (index) {
var html = '';
current = parseInt(index) || parseInt(opts.current); //当前页码
var pageCount = this.getPageCount(); //获取的总页数
switch (opts.mode) { //配置模式
case 'fixed': //固定按钮模式
html += '<li class="page-item"><a href="javascript:;" class="page-link ' + opts.prevCls + '">' + opts.prevContent + '</a></li>';
if (opts.coping) {
var home = opts.coping && opts.homePage ? opts.homePage : '1';
html += '<li class="page-item"><a class="page-link" href="javascript:;" data-page="1">' + home + '</a></li>';
}
var start = current > opts.count - 1 ? current + opts.count - 1 > pageCount ? current - (opts.count - (pageCount - current)) : current - 2 : 1;
var end = current + opts.count - 1 > pageCount ? pageCount : start + opts.count;
for (; start <= end; start++) {
if (start != current) {
html += '<li class="page-item"><a class="page-link" href="javascript:;" data-page="' + start + '">' + start + '</a></li>';
} else {
html += '<li class="page-item active"><span class="page-link ' + opts.activeCls + '">' + start + '</span></li>';
}
}
if (opts.coping) {
var _end = opts.coping && opts.endPage ? opts.endPage : pageCount;
html += '<li class="page-item"><a class="page-link" href="javascript:;" data-page="' + pageCount + '">' + _end + '</a></li>';
}
html += '<li class="page-item"><a href="javascript:;" class="page-link ' + opts.nextCls + '">' + opts.nextContent + '</a></li>';
break;
// if (opts.keepShowPN || current > 1) { //上一页
// html += '<a href="javascript:;" class="' + opts.prevCls + '">' + opts.prevContent + '</a>';
// } else {
// if (opts.keepShowPN == false) {
// $obj.find('.' + opts.prevCls) && $obj.find('.' + opts.prevCls).remove();
// }
// }
// if (current >= opts.count + 2 && current != 1 && pageCount != opts.count) {
// var home = opts.coping && opts.homePage ? opts.homePage : '1';
// html += opts.coping ? '<a href="javascript:;" data-page="1">' + home + '</a><span>...</span>' : '';
// }
// var start = (current - opts.count) <= 1 ? 1 : (current - opts.count);
// var end = (current + opts.count) >= pageCount ? pageCount : (current + opts.count);
// for (; start <= end; start++) {
// if (start <= pageCount && start >= 1) {
// if (start != current) {
// html += '<a href="javascript:;" data-page="' + start + '">' + start + '</a>';
// } else {
// html += '<span class="' + opts.activeCls + '">' + start + '</span>';
// }
// }
// }
// if (current + opts.count < pageCount && current >= 1 && pageCount > opts.count) {
// var end = opts.coping && opts.endPage ? opts.endPage : pageCount;
// html += opts.coping ? '<span>...</span><a href="javascript:;" data-page="' + pageCount + '">' + end + '</a>' : '';
// }
// if (opts.keepShowPN || current < pageCount) { //下一页
// html += '<a href="javascript:;" class="' + opts.nextCls + '">' + opts.nextContent + '</a>';
// } else {
// if (opts.keepShowPN == false) {
// $obj.find('.' + opts.nextCls) && $obj.find('.' + opts.nextCls).remove();
// }
// }
// break;
case 'unfixed': //不固定按钮模式
if (opts.keepShowPN || current > 1) { //上一页
html += '<a href="javascript:;" class="' + opts.prevCls + '">' + opts.prevContent + '</a>';
} else {
if (opts.keepShowPN == false) {
$obj.find('.' + opts.prevCls) && $obj.find('.' + opts.prevCls).remove();
}
}
if (current >= opts.count + 2 && current != 1 && pageCount != opts.count) {
var home = opts.coping && opts.homePage ? opts.homePage : '1';
html += opts.coping ? '<a href="javascript:;" data-page="1">' + home + '</a><span>...</span>' : '';
}
var start = (current - opts.count) <= 1 ? 1 : (current - opts.count);
var end = (current + opts.count) >= pageCount ? pageCount : (current + opts.count);
for (; start <= end; start++) {
if (start <= pageCount && start >= 1) {
if (start != current) {
html += '<a href="javascript:;" data-page="' + start + '">' + start + '</a>';
} else {
html += '<span class="' + opts.activeCls + '">' + start + '</span>';
}
}
}
if (current + opts.count < pageCount && current >= 1 && pageCount > opts.count) {
var end = opts.coping && opts.endPage ? opts.endPage : pageCount;
html += opts.coping ? '<span>...</span><a href="javascript:;" data-page="' + pageCount + '">' + end + '</a>' : '';
}
if (opts.keepShowPN || current < pageCount) { //下一页
html += '<a href="javascript:;" class="' + opts.nextCls + '">' + opts.nextContent + '</a>';
} else {
if (opts.keepShowPN == false) {
$obj.find('.' + opts.nextCls) && $obj.find('.' + opts.nextCls).remove();
}
}
break;
case 'easy': //简单模式
break;
default:
}
html += opts.jump ? '<input type="text" class="' + opts.jumpIptCls + '"><a href="javascript:;" class="' + opts.jumpBtnCls + '">' + opts.jumpBtn + '</a>' : '';
$obj.empty().html(html);
};
//绑定事件
this.eventBind = function () {
var that = this;
var pageCount = that.getPageCount(); //总页数
var index = 1;
$obj.off().on('click', 'a', function () {
if ($(this).hasClass(opts.nextCls)) {
if (parseInt($obj.find('.' + opts.activeCls).text()) >= pageCount) {
$(this).addClass('disabled');
return false;
} else {
index = parseInt($obj.find('.' + opts.activeCls).text()) + 1;
}
} else if ($(this).hasClass(opts.prevCls)) {
if (parseInt($obj.find('.' + opts.activeCls).text()) <= 1) {
$(this).addClass('disabled');
return false;
} else {
index = parseInt($obj.find('.' + opts.activeCls).text()) - 1;
}
} else if ($(this).hasClass(opts.jumpBtnCls)) {
if ($obj.find('.' + opts.jumpIptCls).val() !== '') {
index = parseInt($obj.find('.' + opts.jumpIptCls).val());
} else {
return;
}
} else {
index = parseInt($(this).data('page'));
}
that.filling(index);
typeof opts.callback === 'function' && opts.callback(that);
});
//输入跳转的页码
$obj.on('input propertychange', '.' + opts.jumpIptCls, function () {
var $this = $(this);
var val = $this.val();
var reg = /[^\d]/g;
if (reg.test(val)) $this.val(val.replace(reg, ''));
(parseInt(val) > pageCount) && $this.val(pageCount);
if (parseInt(val) === 0) $this.val(1); //最小值为1
});
//回车跳转指定页码
$document.keydown(function (e) {
if (e.keyCode == 13 && $obj.find('.' + opts.jumpIptCls).val()) {
var index = parseInt($obj.find('.' + opts.jumpIptCls).val());
that.filling(index);
typeof opts.callback === 'function' && opts.callback(that);
}
});
};
//初始化
this.init = function () {
this.filling(opts.current);
this.eventBind();
if (opts.isHide && this.getPageCount() == '1' || this.getPageCount() == '0') {
$obj.hide();
} else {
$obj.show();
}
};
this.init();
};
$.fn.pagination = function (parameter, callback) {
if (typeof parameter == 'function') { //重载
callback = parameter;
parameter = {};
} else {
parameter = parameter || {};
callback = callback || function () {};
}
var options = $.extend({}, defaults, parameter);
return this.each(function () {
var pagination = new Pagination(this, options);
callback(pagination);
});
};
}));
+141
View File
@@ -0,0 +1,141 @@
;jQuery( function() {
// 工具提示
if($('[data-toggle="tooltip"]')[0]) {
$('[data-toggle="tooltip"]').tooltip({
"container" : 'body',
});
}
// POP弹出框
if($('[data-toggle="popover"]')[0]) {
$('[data-toggle="popover"]').popover();
}
// 关闭卡片
$(document).on('click', '.card-btn-close', function() {
$(this).closest('.card').fadeOut(150, function() {
if ($(this).parent().children().length == 1) {
$(this).parent().remove();
} else {
$(this).remove();
}
});
});
// 卡片收缩与打开
$(document).on('click', '.card-btn-slide', function(){
$(this).toggleClass('rotate-180').closest('.card').find('.card-body').slideToggle();
});
/**
* 如果页面中需要用到滚动条,请先导入perfect-scrollbar.min.js
*/
if($('.lyear-scroll')[0]) {
$('.lyear-scroll').each(function(){
new PerfectScrollbar(this, {
swipeEasing: false,
suppressScrollX: true
});
});
}
// 颜色选取
jQuery('.js-colorpicker').each(function() {
var $colorpicker = jQuery(this);
var $colorpickerMode = $colorpicker.data('colorpicker-mode') ? $colorpicker.data('colorpicker-mode') : 'auto';
$colorpicker.colorpicker({
'format': $colorpickerMode,
});
});
// 日期选择器
jQuery("[data-provide = 'datepicker']").each(function() {
var options = {
language: 'zh-CN', // 默认简体中文
multidateSeparator: ', ' // 默认多个日期用,分隔
}
options = $.extend( options, getDataOptions( $(this) ));
if ( $(this).prop("tagName") != 'INPUT' ) {
options.inputs = [$(this).find('input:first'), $(this).find('input:last')];
}
$(this).datepicker(options);
});
// 时间选择器
jQuery("[data-provide = 'clockpicker']").each(function() {
$(this).clockpicker({
donetext: 'Done'
});
});
// 时间日期选择器
jQuery("[data-provide = 'datetimepicker']").each(function() {
var options = {
locale: moment.locale(),
}
options = $.extend( options, getDataOptions( $(this) ));
if ( $(this).prop("tagName") != 'INPUT' ) {
options.inputs = [$(this).find('input:first'), $(this).find('input:last')];
}
console.log(options);
$(this).datetimepicker(options);
});
// 标签
$('.js-tags-input').each(function() {
var $this = $(this);
$this.tagsInput({
height: $this.data('height') ? $this.data('height') : '36px',
width: '100%',
defaultText: $this.attr("placeholder"),
removeWithBackspace: true,
delimiter: [',']
});
});
// 复选框全选
$("#check-all").change(function () {
if ($boxname = $(this).data('name')) {
$(this).closest('table').find("input[name='" + $boxname + "']").prop('checked', $(this).prop("checked"));
} else {
$(this).closest('table').find(".custom-checkbox input[type='checkbox']").prop('checked', $(this).prop("checked"));
}
});
// iframe打开tab
$(document).on('click', '.js-create-tab', function(){
parent.$(parent.document).data('multitabs').create({
iframe : true,
title : $(this).data('title') ? $(this).data('title') : '标题',
url : $(this).data('url') ? $(this).data('url') : '/dashboard'
}, true);
});
});
// 参考国外模板的写法,获取当前的配置,以data-*(*指插件原有的配置名)
getDataOptions = function(el, castList) {
var options = {};
$.each( $(el).data(), function(key, value){
key = dataToOption(key);
if ( key == 'provide' ) {
return;
}
options[key] = value;
});
return options;
}
dataToOption = function(name) {
return name.replace(/-([a-z])/g, function(x){return x[1].toUpperCase();});
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+164
View File
@@ -0,0 +1,164 @@
<!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/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
<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-12">
<div class="card">
<div class="card-header">
<div class="card-title">新增管理员</div>
</div>
<div class="card-body">
<form>
<div class="form-group">
<label>用户名</label>
<input type="text" class="form-control" maxlength="10" id="username"
placeholder="请输入用户名">
</div>
<div class="form-group">
<label>昵称</label>
<input type="text" class="form-control" maxlength="10" id="nickname"
placeholder="请输入昵称">
</div>
<div class="form-group">
<label>手机号</label>
<input type="text" class="form-control" maxlength="11" id="mobile"
placeholder="请输入手机号">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" class="form-control" maxlength="20" id="password"
placeholder="请输入密码">
</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/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript" src="../../bootstrap/js/authorization/md5.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("input#username").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$("input#nickname").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$("input#mobile").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$("input#password").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$('#btnOk').on('click', function () {
const username = $("#username").val();
if (username === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入用户名。',
});
return false;
}
const nickname = $("#nickname").val();
if (nickname === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入昵称。',
});
return false;
}
const password = $("#password").val();
if (password === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入密码。',
});
return false;
}
const postData = {
username: username,
nickname: nickname,
mobile: $("#mobile").val(),
password: md5(password),
};
AjaxForm(
"POST",
"/api/admin",
postData,
function () {
$(this).hide();
$("#btnLoading").show();
},
function (data) {
$("#btnLoading").hide();
$("#btnOk").show();
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '编号:' + data.id + ' 创建完成。',
buttons: {
okay: {
text: '关闭',
action: function () {
location.href = "/admin/list";
}
}
}
});
},
function (response) {
$("#btnLoading").hide();
$("#btnOk").show();
AjaxError(response);
}
);
});
})
</script>
</body>
</html>
+328
View File
@@ -0,0 +1,328 @@
<!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/materialdesignicons.min.css" rel="stylesheet">
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-toolbar d-flex flex-column flex-md-row">
<div class="toolbar-btn-action">
<a class="btn btn-primary m-r-5" href="/admin/add"><i class="mdi mdi-plus"></i> 新增</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>编号</th>
<th>用户名</th>
<th>昵称</th>
<th>手机号</th>
<th>创建日期</th>
<th>更新日期</th>
<th style="text-align: center; ">状态</th>
<th style="text-align: center; ">操作</th>
</tr>
</thead>
<tbody class="tbody">
</tbody>
</table>
</div>
<ul class="pagination">
<ul class="pagination" id="paginationDiv">
</ul>
</ul>
</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" src="../../bootstrap/js/jquery.pagination.js"></script>
<script type="text/javascript">
$(document).ready(function () {
// 加载列表页数据
getPageListData();
function getPageListData(page = 0, page_size = 0) {
if (parseInt(page) < 1) {
page = 1;
}
if (parseInt(page_size) < 1) {
page_size = 10;
}
AjaxForm(
"GET",
"/api/admin",
{page: page, page_size: page_size},
function () {},
function (data) {
if (data.list.length > 0) {
var totalNum = data.pagination.total; //总条数
var pageNum = Math.ceil(totalNum / data.pagination.pre_page_count); //分页的总页数
$("#paginationDiv").pagination({
current: data.pagination.current_page,
pageCount: pageNum,
coping: true,
homePage: '首页',
endPage: '末页',
mode: 'fixed',
prevContent: '上一页',
nextContent: '下一页',
activeCls: 'pageActive',
prevCls: 'pagePrev',
nextCls: 'pageNext',
callback: function (api) {
$(".tbody").html("");
getPageListData(api.getCurrent());
}
});
$.each(data.list, function (index, value) {
var showUsedBadge = "";
var optionUsedName = "";
if (value.is_used === 1) {
optionUsedName = '禁用';
showUsedBadge = '<span class="badge badge-success">启用</span></td>'
}
if (value.is_used === -1) {
optionUsedName = '启用';
showUsedBadge = '<span class="badge badge-danger">禁用</span></td>'
}
const tr = '<tr>\n' +
'<td>' + value.id + '</td>\n' +
'<td>' + value.username + '</td>\n' +
'<td>' + value.nickname + '</td>\n' +
'<td>' + value.mobile + '</td>\n' +
'<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; ">\n' +
'<div class="btn-group">\n' +
' <a class="btn btn-xs btn-default btn-option" href="#!" title=""\n' +
' data-id="' + value.hashid + '"' +
' data-is-used="' + value.is_used + '"' +
' data-toggle="tooltip" data-original-title="' + optionUsedName + '">' + optionUsedName + '</a>\n' +
' <a class="btn btn-xs btn-default btn-resetPassword" 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' +
'</div>\n' +
'</td>\n' +
'</tr>';
$(".tbody").append(tr);
})
} else {
// 数据为空
const tr = '<tr><td colspan="8" style="text-align: center">暂无数据</td></tr>';
$(".tbody").append(tr);
}
},
function (response) {
AjaxError(response);
}
);
}
// 启用/禁用
$(document).on('click', '.btn-option', function () {
const id = $(this).attr('data-id');
const isUsed = $(this).attr('data-is-used');
var tipMessage = "";
var wantUsed = 0;
if (isUsed === "1") { // 1=当前为启用状态,需要改成禁用
tipMessage = "禁用";
wantUsed = -1;
}
if (isUsed === "-1") { // -1=当前为禁用状态,需要改成启用
tipMessage = "启用";
wantUsed = 1;
}
const patchData = {
id: id,
used: wantUsed,
};
$.confirm({
title: '谨慎操作',
content: '确认要 <strong style="color: red">' + tipMessage + '</strong> 吗?',
icon: 'mdi mdi-alert',
animation: 'scale',
closeAnimation: 'zoom',
buttons: {
okay: {
text: '确认',
keys: ['enter'],
btnClass: 'btn-orange',
action: function () {
AjaxForm(
"PATCH",
"/api/admin/used",
patchData,
function () {},
function (data) {
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '编号:' + data.id + ' 已' + tipMessage + '。',
buttons: {
okay: {
text: '关闭',
action: function () {
location.reload();
}
}
}
});
},
function (response) {
AjaxError(response);
}
);
}
},
cancel: {
text: '取消',
keys: ['ctrl', 'shift'],
}
}
});
});
// 重置密码
$(document).on('click', '.btn-resetPassword', function () {
const id = $(this).attr('data-id');
$.confirm({
title: '谨慎操作',
content: '确认要 <strong style="color: red">重置密码</strong> 吗?' + '<hr>' + '重置后密码为:123456',
icon: 'mdi mdi-alert',
animation: 'scale',
closeAnimation: 'zoom',
buttons: {
okay: {
text: '确认',
keys: ['enter'],
btnClass: 'btn-orange',
action: function () {
AjaxForm(
"PATCH",
'/api/admin/reset_password/' + 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');
$.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(
"DELETE",
'/api/admin/' + 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'],
}
}
});
})
})
</script>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
<title>登录页面</title>
<link rel="shortcut icon" type="image/x-icon" href="bootstrap/favicon.ico">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<link rel="stylesheet" type="text/css" href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css">
<link rel="stylesheet" type="text/css" href="../../bootstrap/css/materialdesignicons.min.css">
<link rel="stylesheet" type="text/css" href="../../bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="../../bootstrap/css/style.min.css">
<style>
.login-form .has-feedback {
position: relative;
}
.login-form .has-feedback .form-control {
padding-left: 36px;
}
.login-form .has-feedback .mdi {
position: absolute;
top: 0;
left: 0;
right: auto;
width: 36px;
height: 36px;
line-height: 36px;
z-index: 4;
color: #dcdcdc;
display: block;
text-align: center;
pointer-events: none;
}
.login-form .has-feedback.row .mdi {
left: 15px;
}
</style>
</head>
<body class="center-vh">
<div class="card card-shadowed p-5 w-420 mb-0 mr-2 ml-2">
<div class="text-center mb-3">
<img src="../../bootstrap/images/logo-sidebar.png">
</div>
<form class="login-form">
<div class="form-group has-feedback">
<span class="mdi mdi-account" aria-hidden="true"></span>
<input type="text" class="form-control" id="username" placeholder="用户名">
</div>
<div class="form-group has-feedback">
<span class="mdi mdi-lock" aria-hidden="true"></span>
<input type="password" class="form-control" id="password" placeholder="密码">
</div>
<div class="form-group">
<button class="btn btn-block btn-primary" id="btnOk" type="button">立即登录</button>
</div>
<div class="alert alert-primary" style="text-align: center;" role="alert">
默认
<span class="mdi mdi-account" aria-hidden="true"></span> admin
<span class="mdi mdi-lock" aria-hidden="true"></span> admin
</div>
</form>
</div>
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript" src="../../bootstrap/js/authorization/md5.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').on('click', function () {
const username = $("#username").val();
if (username === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入用户名。',
});
return false;
}
const password = $("#password").val();
if (password === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入密码。',
});
return false;
}
const postData = {
username: username,
password: md5(password),
};
AjaxForm(
"POST",
"/login/web",
postData,
function () {},
function (data) {
let date = new Date();
date.setTime(date.getTime() + 24 * 60 * 60 * 1000); // 24 * 60 * 60 * 1000 表示 24 小时
$.cookie('_login_token_', data.token, {expires: date});
location.href = "/";
},
function (response) {
AjaxError(response);
}
);
});
})
</script>
</body>
</html>
@@ -0,0 +1,131 @@
<!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/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
<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-12">
<div class="card">
<div class="card-header">
<div class="card-title">个人信息</div>
</div>
<div class="card-body">
<form class="site-form">
<div class="form-group">
<label>用户名</label>
<input type="text" class="form-control" id="username" disabled="disabled">
</div>
<div class="form-group">
<label>昵称</label>
<input type="text" class="form-control" maxlength="10" id="nickname" placeholder="输入您的昵称">
</div>
<div class="form-group">
<label>手机号</label>
<input type="text" class="form-control" maxlength="11" id="mobile" placeholder="请输入您的手机号">
</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/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("input#nickname, input#mobile").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
AjaxForm(
"GET",
"/api/admin/info",
"",
function () {},
function (data) {
$("#username").val(data.username);
$("#nickname").val(data.nickname);
$("#mobile").val(data.mobile);
},
function (response) {
AjaxError(response);
}
);
$('#btnOk').on('click', function () {
const nickname = $("#nickname").val();
if (nickname === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入您的昵称。',
});
return false;
}
const postData = {
nickname: nickname,
mobile: $("#mobile").val(),
};
AjaxForm(
"PATCH",
"/api/admin/modify_personal_info",
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>
@@ -0,0 +1,153 @@
<!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/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
<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-12">
<div class="card">
<div class="card-header">
<div class="card-title">新增管理员</div>
</div>
<div class="card-body">
<form>
<div class="form-group">
<label>旧密码</label>
<input type="password" class="form-control" maxlength="20" id="old_password"
placeholder="输入账号的原登录密码">
</div>
<div class="form-group">
<label>新密码</label>
<input type="password" class="form-control" maxlength="20" id="new_password"
placeholder="输入新的密码">
</div>
<div class="form-group">
<label>确认新密码</label>
<input type="password" class="form-control" maxlength="20" id="confirm_password"
placeholder="再次输入新的密码">
</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/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript" src="../../bootstrap/js/authorization/md5.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("input#old_password, input#new_password, input#confirm_password").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$('#btnOk').on('click', function () {
const old_password = $("#old_password").val();
if (old_password === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入账号的原登录密码。',
});
return false;
}
const new_password = $("#new_password").val();
if (new_password === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入新的密码。',
});
return false;
}
const confirm_password = $("#confirm_password").val();
if (confirm_password === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请再次输入新的密码。',
});
return false;
}
if (new_password !== confirm_password) {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请确认两次密码一致。',
});
return false;
}
const postData = {
old_password: md5(old_password),
new_password: md5(new_password),
};
AjaxForm(
"PATCH",
"/api/admin/modify_password",
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 () {
parent.window.close();
window.open("/login");
}
}
}
});
},
function (response) {
$("#btnLoading").hide();
$("#btnOk").show();
AjaxError(response);
}
);
});
})
</script>
</body>
</html>
@@ -0,0 +1,142 @@
<!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/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
<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-12">
<div class="card">
<div class="card-header">
<div class="card-title">新增调用方</div>
</div>
<div class="card-body">
<form>
<div class="form-group">
<label for="formGroupExampleInput">调用方</label>
<input type="text" class="form-control" maxlength="25" id="business_key"
placeholder="请输入调用方标识">
</div>
<div class="form-group">
<label for="formGroupExampleInput2">调用方对接人</label>
<input type="text" class="form-control" maxlength="50" id="business_developer"
placeholder="请输入调用方对接人">
</div>
<div class="form-group">
<label for="exampleFormControlTextarea1">备注</label>
<textarea class="form-control" maxlength="225" rows="3" id="remark"
placeholder="备注"></textarea>
</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/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("input#business_key").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$("input#business_developer").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$("textarea#remark").maxlength({
threshold: 255,
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$('#btnOk').on('click', function () {
const businessKey = $("#business_key").val();
if (businessKey === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入调用方标识。',
});
return false;
}
const businessDeveloper = $("#business_developer").val();
if (businessDeveloper === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入调用方对接人。',
});
return false;
}
const postData = {
business_key: businessKey,
business_developer: businessDeveloper,
remark: $("#remark").val(),
};
AjaxForm(
"POST",
"/api/authorized",
postData,
function () {
$(this).hide();
$("#btnLoading").show();
},
function (data) {
$("#btnLoading").hide();
$("#btnOk").show();
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '编号:' + data.id + ' 创建完成。',
buttons: {
okay: {
text: '关闭',
action: function () {
location.href = "/authorized/list";
}
}
}
});
},
function (response) {
$("#btnLoading").hide();
$("#btnOk").show();
AjaxError(response);
}
);
});
})
</script>
</body>
</html>
@@ -0,0 +1,259 @@
<!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/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
<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">
<div class="card-header">
<div class="card-title">接口授权</div>
</div>
<div class="card-body">
<div class="alert alert-warning" role="alert">
接口地址支持通配符(*),其中 * 表示 1 级,** 表示 n 级。
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="request_method">选择请求方式</label>
</div>
<select class="custom-select" id="request_method">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
</select>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">输入接口地址</span>
</div>
<input type="text" class="form-control" maxlength="60" id="request_api"
placeholder="接口地址">
</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 apis">
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript">
$(document).ready(function () {
const hash_id = {{ .HashID }}
$("input#request_api").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
// 加载列表页数据
getListData();
function getListData() {
AjaxForm(
"GET",
"/api/authorized_api",
{id: hash_id},
function () {
},
function (data) {
if (data.list.length > 0) {
var badgeMethodClass = "";
$.each(data.list, function (index, value) {
if (value.method === "GET") {
badgeMethodClass = "badge-primary";
} else if (value.method === "POST") {
badgeMethodClass = "badge-success";
} else if (value.method === "DELETE") {
badgeMethodClass = "badge-danger";
} else if (value.method === "PUT") {
badgeMethodClass = "badge-yellow";
} else if (value.method === "PATCH") {
badgeMethodClass = "badge-cyan";
} else {
badgeMethodClass = "badge-dark";
}
const p = '<p>\n' +
'<a href="#!" data-id="' + value.hash_id + '" data-api="' + value.api + '" class="del">' +
'<span class="badge badge-dark"><i class="mdi mdi-window-close"></i></span>\n' +
'</a>\n' +
'<span class="badge ' + badgeMethodClass + '">' + value.method + '</span>\n' + value.api
;
$(".apis").append(p);
})
} else {
// 数据为空
const p = '<p>暂无授权接口</p>';
$(".apis").append(p);
}
},
function (response) {
AjaxError(response);
}
);
}
$('#btnOk').on('click', function () {
const requestMethod = $("#request_method").val();
const requestApi = $("#request_api").val();
if (requestMethod === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请选择请求方式。',
});
return false;
}
if (requestApi === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入请求地址。',
});
return false;
}
const postData = {
method: requestMethod,
api: requestApi,
id: hash_id,
};
AjaxForm(
"POST",
"/api/authorized_api",
postData,
function () {
$(this).hide();
$("#btnLoading").show();
},
function () {
$("#btnLoading").hide();
$("#btnOk").show();
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '接口:' + requestApi + ' 授权完成。',
buttons: {
okay: {
text: '关闭',
action: function () {
location.reload();
}
}
}
});
},
function (response) {
$("#btnLoading").hide();
$("#btnOk").show();
AjaxError(response);
}
);
});
$(document).on('click', '.del', function () {
const id = $(this).attr('data-id');
const api = $(this).attr('data-api');
$.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(
"DELETE",
'/api/authorized_api/' + id,
"",
function () {
},
function () {
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '接口:' + api + ' 已取消授权。',
buttons: {
okay: {
text: '关闭',
action: function () {
location.reload();
}
}
}
});
},
function (response) {
AjaxError(response);
}
);
}
},
cancel: {
text: '取消',
keys: ['ctrl', 'shift'],
}
}
});
})
})
</script>
</body>
</html>
@@ -0,0 +1,214 @@
<!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/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-12">
<div class="card">
<header class="card-header">
<div class="card-title">如何给调用方开通 KEY 和 SECRET?</div>
</header>
<div class="card-body">
<p>1. 新增调用方,输入调用方标识、调用方对接人、备注等信息;</p>
<p>2. 授权调用方可调用的接口;</p>
<p>3. 查看详情,将调用方的 <code>KEY</code><code>SECRET</code> 发给调用方;</p>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card">
<header class="card-header">
<div class="card-title">调用方如何传递 Token</div>
</header>
<div class="card-body">
<p>基于 HTTP Header 中的两个参数 <code>Authorization</code><code>Authorization-Date</code> 存储签名信息。</p>
<p>1. Authorization 存储签名信息,格式:调用方 KEY + 空格分隔符 + 摘要(加密串),例如:</p>
<pre>Authorization:blog MjJjMDE1MWFkZjMwOWFmYjFlNzViNDFjYjYwMWFlMmM=</pre>
<p>2. Authorization-Date 存储时间信息,格式:0000-00-00 00:00:00,使用 <code>Asia/Shanghai</code> 时区,例如;</p>
<pre>Authorization-Date:2021-04-03 21:12:36</pre>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card">
<header class="card-header"><div class="card-title">不同语言生成签名的方法,供参考</div></header>
<div class="card-body">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#go" aria-selected="true">Go 语言</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#php" aria-selected="false">PHP 语言</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#js" aria-selected="false">JS 语言</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade active show" id="go">
<pre>
func New(key, secret string, ttl time.Duration) Signature {
return &signature{
key: key,
secret: secret,
ttl: ttl,
}
}
// Generate
// path 请求的路径 (不附带 querystring)
func (s *signature) Generate(path string, method string, params url.Values) (authorization, date string, err error) {
if path == "" {
err = errors.New("path required")
return
}
if method == "" {
err = errors.New("method required")
return
}
methodName := strings.ToUpper(method)
if !methods[methodName] {
err = errors.New("method param error")
return
}
// Date
date = time_parse.CSTLayoutString()
// Encode() 方法中自带 sorted by key
sortParamsEncode, err := url.QueryUnescape(params.Encode())
if err != nil {
err = errors.Errorf("url QueryUnescape %v", err)
return
}
// 加密字符串规则
buffer := bytes.NewBuffer(nil)
buffer.WriteString(path)
buffer.WriteString(delimiter)
buffer.WriteString(methodName)
buffer.WriteString(delimiter)
buffer.WriteString(sortParamsEncode)
buffer.WriteString(delimiter)
buffer.WriteString(date)
// 对数据进行 sha256 加密,并进行 base64 encode
hash := hmac.New(sha256.New, []byte(s.secret))
hash.Write(buffer.Bytes())
digest := base64.StdEncoding.EncodeToString(hash.Sum(nil))
authorization = fmt.Sprintf("%s %s", s.key, digest)
return
}
// 模拟数据
const (
key = "blog"
secret = "i1ydX9RtHyuJTrw7frcu"
ttl = time.Minute * 10
)
func TestSignature_Generate(t *testing.T) {
path := "/echo"
method := "POST"
params := url.Values{}
params.Add("a", "a1")
params.Add("d", "d1")
params.Add("c", "c1 c2*")
authorization, date, err := New(key, secret, ttl).Generate(path, method, params)
t.Log("authorization:", authorization)
t.Log("authorization-date:", date)
t.Log("err:", err)
}
</pre>
</div>
<div class="tab-pane fade" id="php">
<pre>
// 模拟数据
$key = "blog";
$secret = "i1ydX9RtHyuJTrw7frcu";
$path = "/echo";
$method = "POST";
$params['a'] = "a1";
$params['d'] = "d1";
$params['c'] = "c1 c2*";
// 对 params key 进行排序
ksort($params);
// 对 sortParams 进行操作
$sortParamsEncode = rawurldecode(http_build_query($params, "", "&", PHP_QUERY_RFC3986));
// 时间 使用 Asia/Shanghai 时区
$date = date("Y-m-d H:i:s", time());
// 加密字符串规则
$encryptStr = $path."|".strtoupper($method)."|".$sortParamsEncode."|".$date;
// 对数据进行 sha256 加密,并进行 base64 encode
$digest = base64_encode(hash_hmac("sha256", $encryptStr, $secret, true));
$authorization = $key." ".$digest;
echo "authorization:{$authorization}";
echo "---";
echo "authorization-date:{$date}";
</pre>
</div>
<div class="tab-pane fade" id="js">
<pre>
let key = "blog";
let secret = "i1ydX9RtHyuJTrw7frcu";
let date = new Date();
let datetime = date.getFullYear() + "-" // "年"
+ ((date.getMonth() + 1) > 10 ? (date.getMonth() + 1) : "0" + (date.getMonth() + 1)) + "-" // "月"
+ (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " " // ""
+ (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":" // "小时"
+ (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":" // "分钟"
+ (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds()); // ""
let path = "/echo";
let method = "POST";
let params = {a:'a1', d:'d1', c: 'c1 c2*'};
let sortParamsEncode = decodeURIComponent(jQuery.param(ksort(params)));
let encryptStr = path + "|" + method.toUpperCase() + "|" + sortParamsEncode + "|" + datetime;
let digest = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(encryptStr, secret));
console.log({authorization: key + " " + digest, date: datetime});
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/main.min.js"></script>
</body>
</html>
@@ -0,0 +1,294 @@
<!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/materialdesignicons.min.css" rel="stylesheet">
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-toolbar d-flex flex-column flex-md-row">
<div class="toolbar-btn-action">
<a class="btn btn-primary m-r-5" href="/authorized/add"><i class="mdi mdi-plus"></i> 新增</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>编号</th>
<th>调用方</th>
<th>对接人</th>
<th>创建日期</th>
<th>更新日期</th>
<th style="text-align: center; ">状态</th>
<th style="text-align: center; ">操作</th>
</tr>
</thead>
<tbody class="tbody">
</tbody>
</table>
</div>
<ul class="pagination">
<ul class="pagination" id="paginationDiv">
</ul>
</ul>
</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" src="../../bootstrap/js/jquery.pagination.js"></script>
<script type="text/javascript">
$(document).ready(function () {
// 加载列表页数据
getPageListData();
function getPageListData(page = 0, page_size = 0) {
if (parseInt(page) < 1) {
page = 1;
}
if (parseInt(page_size) < 1) {
page_size = 10;
}
AjaxForm(
"GET",
"/api/authorized",
{page: page, page_size: page_size},
function () {},
function (data) {
if (data.list.length > 0) {
var totalNum = data.pagination.total; //总条数
var pageNum = Math.ceil(totalNum / data.pagination.pre_page_count); //分页的总页数
$("#paginationDiv").pagination({
current: data.pagination.current_page,
pageCount: pageNum,
coping: true,
homePage: '首页',
endPage: '末页',
mode: 'fixed',
prevContent: '上一页',
nextContent: '下一页',
activeCls: 'pageActive',
prevCls: 'pagePrev',
nextCls: 'pageNext',
callback: function (api) {
$(".tbody").html("");
getPageListData(api.getCurrent());
}
});
$.each(data.list, function (index, value) {
var showUsedBadge = "";
var optionUsedName = "";
if (value.is_used === 1) {
optionUsedName = '禁用';
showUsedBadge = '<span class="badge badge-success">启用</span></td>'
}
if (value.is_used === -1) {
optionUsedName = '启用';
showUsedBadge = '<span class="badge badge-danger">禁用</span></td>'
}
const tr = '<tr>\n' +
'<td>' + value.id + '</td>\n' +
'<td>' + value.business_key + '</td>\n' +
'<td>' + value.business_developer + '</td>\n' +
'<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; ">\n' +
'<div class="btn-group">\n' +
' <a class="btn btn-xs btn-default btn-detail" href="#!" title=""\n' +
' data-business-key="' + value.business_key + '"' +
' data-business-secret="' + value.business_secret + '"' +
' data-remark="' + value.remark + '"' +
' data-toggle="tooltip" data-original-title="详情">详情</a>\n' +
' <a class="btn btn-xs btn-default btn-option" href="#!" title=""\n' +
' data-id="' + value.hashid + '"' +
' data-is-used="' + value.is_used + '"' +
' data-toggle="tooltip" data-original-title="' + optionUsedName + '">' + optionUsedName + '</a>\n' +
' <a class="btn btn-xs btn-default" href="/authorized/api/'+value.hashid+'" title=""\n' +
' 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' +
'</div>\n' +
'</td>\n' +
'</tr>';
$(".tbody").append(tr);
})
} else {
// 数据为空
const tr = '<tr><td colspan="7" style="text-align: center">暂无数据</td></tr>';
$(".tbody").append(tr);
}
},
function (response) {
AjaxError(response);
}
);
}
// 详情
$(document).on('click', '.btn-detail', function () {
const business_key = $(this).attr('data-business-key');
const business_secret = $(this).attr('data-business-secret');
const business_remark = $(this).attr('data-remark');
$.alert({
title: '详情',
content: '调用方 KEY ' + business_key + '<br/>调用方 SECRET ' + business_secret + '<br/>备注:' + business_remark,
type: 'green',
animation: 'scale',
draggable: true,
});
});
// 启用/禁用
$(document).on('click', '.btn-option', function () {
const id = $(this).attr('data-id');
const isUsed = $(this).attr('data-is-used');
var tipMessage = "";
var wantUsed = 0;
if (isUsed === "1") { // 1=当前为启用状态,需要改成禁用
tipMessage = "禁用";
wantUsed = -1;
}
if (isUsed === "-1") { // -1=当前为禁用状态,需要改成启用
tipMessage = "启用";
wantUsed = 1;
}
const patchData = {
id: id,
used: wantUsed,
};
$.confirm({
title: '谨慎操作',
content: '确认要 <strong style="color: red">' + tipMessage + '</strong> 吗?',
icon: 'mdi mdi-alert',
animation: 'scale',
closeAnimation: 'zoom',
buttons: {
okay: {
text: '确认',
keys: ['enter'],
btnClass: 'btn-orange',
action: function () {
AjaxForm(
"PATCH",
"/api/authorized/used",
patchData,
function () {},
function (data) {
$.alert({
title: '操作成功',
icon: 'mdi mdi-check-decagram',
type: 'green',
content: '编号:' + data.id + ' 已' + tipMessage + '。',
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');
$.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(
"DELETE",
'/api/authorized/' + 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'],
}
}
});
})
})
</script>
</body>
</html>
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-12">
<div class="card">
<header class="card-header"><div class="card-title"> 配置信息</div></header>
<div class="card-body">
<h5 class="card-title">MySQL</h5>
<p>主库信息:{{.MySQL.Write.Addr}},账号:{{.MySQL.Write.User}},数据库:{{.MySQL.Write.Name}}</p>
<p>从库信息:{{.MySQL.Read.Addr}},账号:{{.MySQL.Read.User}},数据库:{{.MySQL.Read.Name}}</p>
<p>最大连接数:{{ .MySQL.Base.MaxOpenConn }}</p>
<p>空闲连接数:{{ .MySQL.Base.MaxIdleConn }}</p>
</div>
<div class="card-body">
<h5 class="card-title">Redis</h5>
<p>地址:{{ .Redis.Addr }} </p>
<p>最大连接数:{{ .Redis.PoolSize }} </p>
<p>空闲连接数:{{ .Redis.MinIdleConns }} </p>
</div>
<div class="card-body">
<h5 class="card-title">Mail</h5>
<p>邮箱服务器:{{ .Mail.Host }} </p>
<p>发件人邮箱地址:{{ .Mail.User }} </p>
<p>收件人邮箱地址:{{ .Mail.To }} </p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
+89
View File
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">内存信息</div>
</header>
<div class="card-body">
<p>总量:{{ .MemTotal }}</p>
<p>已使用:{{ .MemUsed }}</p>
<p>使用率:<span class="badge
{{if gt .MemUsedPercent 95.0}} badge-danger
{{else if gt .MemUsedPercent 90.0 }} badge-warning
{{else}} badge-success
{{end}} ">{{ .MemUsedPercent }}%</span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">硬盘信息</div>
</header>
<div class="card-body">
<p>总量:{{ .DiskTotal }}</p>
<p>已使用:{{ .DiskUsed }}</p>
<p>使用率:<span class="badge
{{if gt .DiskUsedPercent 95.0}} badge-danger
{{else if gt .DiskUsedPercent 90.0 }} badge-warning
{{else}} badge-success
{{end}}">{{ .DiskUsedPercent }}%</span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">CPU 信息</div>
</header>
<div class="card-body">
<p>CPU{{ .CpuName }}</p>
<p>核数:{{ .CpuCores }}</p>
<p>CPU 使用率:<span class="badge
{{if gt .CpuUsedPercent 95.0}} badge-danger
{{else if gt .CpuUsedPercent 90.0 }} badge-warning
{{else}} badge-success
{{end}}">{{ .CpuUsedPercent }}%</span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-secondary">
<header class="card-header">
<div class="card-title">项目信息</div>
</header>
<div class="card-body">
<p>操作系统:{{ .GoOS }} <span class="badge badge-brown"> {{ .GoArch }} </span></p>
<p>项目地址:{{ .ProjectPath }}</p>
<p>项目域名:{{ .Host }} <span class="badge badge-secondary"> {{ .Env }} </span></p>
<p>GOPATH{{ .GoPath }}</p>
<p>Version{{ .GoVersion }}</p>
<p>Goroutine{{ .Goroutine }}</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title"> <code>gormgen</code> 代码生成工具</div></header>
<div class="card-body">
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code><code>Linux</code> 环境执行。</div>
<div class="form-group">
<label for="tableSelect">选择数据表,可进行多选:</label>
<select multiple="" class="form-control" id="tableSelect" style="height: 260px;">
{{range .}}
<option value="{{ .Name }}">{{ .Name }} -- 备注:{{ .Comment }}</option>
{{end}}
</select>
</div>
<p>
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
执行中...
</button>
</p>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title">执行结果</div></header>
<div class="card-body">
<pre id="resultDiv"></pre>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#btnOk").click(function(){
var tables = [];
var options = $("#tableSelect").find("option:selected");
if (options.length < 1) {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请选择数据表。',
});
return false;
}
$("#resultDiv").text("");
$(this).hide();
$("#btnLoading").show();
for (var i = 0; i < options.length; i++) {
tables.push(options.eq(i).val()); // 将所有的值赋给数组
}
$.post("/gormgen_exec",{tables:tables.join(',')},function (data) {
$("#resultDiv").text(data);
$("#btnLoading").hide();
$("#btnOk").show();
})
})
})
</script>
</body>
</html>
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
<link href="bootstrap/css/style.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="row">
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title"> <code>handlergen</code> 代码生成工具</div></header>
<div class="card-body">
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code><code>Linux</code> 环境执行。</div>
<div class="form-group">
<label for="exampleFormControlInput1">handler 名称,例如:<code>user_handler</code></label>
<input type="text" class="form-control" id="handlerName" placeholder="请输入 handler 名称">
</div>
<p>
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
执行中...
</button>
</p>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<header class="card-header"><div class="card-title">执行结果</div></header>
<div class="card-body">
<pre id="resultDiv"></pre>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#btnOk").click(function(){
var handlerName = $("#handlerName").val();
if (!handlerName) {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入 handler 名称。',
});
return false;
}
$("#resultDiv").text("");
$(this).hide();
$("#btnLoading").show();
$.post("/handlergen_exec",{name:handlerName},function (data) {
$("#resultDiv").text(data);
$("#btnLoading").hide();
$("#btnOk").show();
})
})
})
</script>
</body>
</html>
@@ -0,0 +1,94 @@
<!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>
+299
View File
@@ -0,0 +1,299 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
<title>管理面板</title>
<link rel="shortcut icon" type="image/x-icon" href="bootstrap/favicon.ico">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<link rel="stylesheet" type="text/css" href="bootstrap/js/jquery-confirm/jquery-confirm.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/css/materialdesignicons.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/js/bootstrap-multitabs/multitabs.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/css/animate.min.css">
<link rel="stylesheet" type="text/css" href="bootstrap/css/style.min.css">
</head>
<body>
<div class="lyear-layout-web">
<div class="lyear-layout-container">
<!--左侧导航-->
<aside class="lyear-layout-sidebar">
<!-- logo -->
<div id="logo" class="sidebar-header">
<a href="/">
<img src="bootstrap/images/logo-sidebar.png"/>
</a>
</div>
<div class="lyear-layout-sidebar-info lyear-scroll">
<nav class="sidebar-main">
<ul class="nav-drawer">
<li class="nav-item active"> <a class="multitabs" href="/dashboard"><i class="mdi mdi-home"></i> <span>仪表盘</span></a> </li>
<li class="nav-item"> <a class="multitabs" href="/configinfo"><i class="mdi mdi-settings-box"></i> <span>配置信息</span></a> </li>
<li class="nav-item nav-item-has-subnav">
<a href="javascript:void(0)"><i class="mdi mdi-code-not-equal-variant"></i> <span>代码生成器</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/init">初始化</a> </li>
<li> <a class="multitabs" href="/gormgen">gormgen</a> </li>
<li> <a class="multitabs" href="/handlergen">handlergen</a> </li>
</ul>
</li>
<li class="nav-item 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>
</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="/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>
<span class="lyear-toggler-bar"></span>
<span class="lyear-toggler-bar"></span>
</div>
</div>
<ul class="navbar-right d-flex align-items-center">
<!--切换主题配色-->
<li class="dropdown dropdown-skin">
<span data-toggle="dropdown" class="icon-item"><i class="mdi mdi-palette"></i></span>
<ul class="dropdown-menu dropdown-menu-right" data-stopPropagation="true">
<li class="drop-title"><p>LOGO</p></li>
<li class="drop-skin-li clearfix">
<span class="inverse">
<input type="radio" name="logo_bg" value="default" id="logo_bg_1" checked>
<label for="logo_bg_1"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_2" id="logo_bg_2">
<label for="logo_bg_2"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_3" id="logo_bg_3">
<label for="logo_bg_3"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_4" id="logo_bg_4">
<label for="logo_bg_4"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_5" id="logo_bg_5">
<label for="logo_bg_5"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_6" id="logo_bg_6">
<label for="logo_bg_6"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_7" id="logo_bg_7">
<label for="logo_bg_7"></label>
</span>
<span>
<input type="radio" name="logo_bg" value="color_8" id="logo_bg_8">
<label for="logo_bg_8"></label>
</span>
</li>
<li class="drop-title"><p>头部</p></li>
<li class="drop-skin-li clearfix">
<span class="inverse">
<input type="radio" name="header_bg" value="default" id="header_bg_1" checked>
<label for="header_bg_1"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_2" id="header_bg_2">
<label for="header_bg_2"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_3" id="header_bg_3">
<label for="header_bg_3"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_4" id="header_bg_4">
<label for="header_bg_4"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_5" id="header_bg_5">
<label for="header_bg_5"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_6" id="header_bg_6">
<label for="header_bg_6"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_7" id="header_bg_7">
<label for="header_bg_7"></label>
</span>
<span>
<input type="radio" name="header_bg" value="color_8" id="header_bg_8">
<label for="header_bg_8"></label>
</span>
</li>
<li class="drop-title"><p>侧边栏</p></li>
<li class="drop-skin-li clearfix">
<span class="inverse">
<input type="radio" name="sidebar_bg" value="default" id="sidebar_bg_1" checked>
<label for="sidebar_bg_1"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_2" id="sidebar_bg_2">
<label for="sidebar_bg_2"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_3" id="sidebar_bg_3">
<label for="sidebar_bg_3"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_4" id="sidebar_bg_4">
<label for="sidebar_bg_4"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_5" id="sidebar_bg_5">
<label for="sidebar_bg_5"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_6" id="sidebar_bg_6">
<label for="sidebar_bg_6"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_7" id="sidebar_bg_7">
<label for="sidebar_bg_7"></label>
</span>
<span>
<input type="radio" name="sidebar_bg" value="color_8" id="sidebar_bg_8">
<label for="sidebar_bg_8"></label>
</span>
</li>
</ul>
</li>
<!--切换主题配色-->
<li class="dropdown dropdown-profile">
<a href="javascript:void(0)" data-toggle="dropdown" class="dropdown-toggle">
<img class="img-avatar img-avatar-48 m-r-10" src="bootstrap/images/users/avatar.png">
<span id="nickname"></span>
</a>
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a class="multitabs dropdown-item" data-url="/admin/modify_info" href="javascript:void(0)">
<i class="mdi mdi-account"></i> 个人信息
</a>
</li>
<li>
<a class="multitabs dropdown-item" data-url="/admin/modify_password" href="javascript:void(0)">
<i class="mdi mdi-lock-outline"></i> 修改密码
</a>
</li>
<li class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="javascript:void(0)" id="logout">
<i class="mdi mdi-logout-variant"></i> 退出登录
</a>
</li>
</ul>
</li>
</ul>
</nav>
</header>
<!--End 头部信息-->
<!--页面主要内容-->
<main class="lyear-layout-content">
<div id="iframe-content"></div>
</main>
<!--End 页面主要内容-->
</div>
</div>
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="bootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bootstrap/js/perfect-scrollbar.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
<script type="text/javascript" src="bootstrap/js/jquery.cookie.min.js"></script>
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
<script type="text/javascript" src="bootstrap/js/index.min.js"></script>
<script type="text/javascript" src="bootstrap/js/httpclient/httpclient.js"></script>
<script type="text/javascript">
$(document).ready(function () {
AjaxForm(
"GET",
"/api/admin/info",
"",
function () {},
function (data) {
$("#nickname").html(data.nickname);
},
function (response) {
AjaxError(response);
}
);
$("#logout").on('click', function () {
AjaxForm(
"POST",
"/api/admin/logout",
"",
function () {},
function () {
// 清空 cookie
$.cookie('_nav_url_', '');
$.cookie('_nav_title_', '');
$.cookie('_login_token_', '');
parent.window.close();
window.open("/login");
},
function (response) {
AjaxError(response);
}
);
})
})
</script>
</body>
</html>
+161
View File
@@ -0,0 +1,161 @@
<!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/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">
<div class="card-header">
<div class="card-title">hashids 加密</div>
</div>
<div class="card-body">
<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="number" maxlength="10" placeholder="需加密的数字">
</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" disabled id="NumberToEncodeValue">
</div>
<button type="button" id="btnEncode" class="btn btn-primary">执行</button>
<button type="button" id="btnEncodeLoading" 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">hashids 解密</div>
</header>
<div class="card-body">
<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="encodeValue" placeholder="需解密的密文">
</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" disabled id="DecodeValueToNumber">
</div>
<button type="button" id="btnDecode" class="btn btn-primary">执行</button>
<button type="button" id="btnDecodeLoading" 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>
</div>
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.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 () {
$("input#number").maxlength({
warningClass: "badge badge-info",
limitReachedClass: "badge badge-warning"
});
$('#btnEncode').on('click', function () {
const number = $("#number").val();
if (number === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入需加密的数字。',
});
return false;
}
AjaxForm(
"GET",
"/api/tool/hashids/encode/" + number,
"",
function () {
$(this).hide();
$("#btnEncodeLoading").show();
},
function (data) {
$("#btnEncodeLoading").hide();
$("#btnEncode").show();
$("#NumberToEncodeValue").val(data.val)
},
function (response) {
$("#btnEncodeLoading").hide();
$("#btnEncode").show();
AjaxError(response);
}
);
});
$('#btnDecode').on('click', function () {
const encodeValue = $("#encodeValue").val();
if (encodeValue === "") {
$.alert({
title: '温馨提示',
icon: 'mdi mdi-alert',
type: 'orange',
content: '请输入需解密的密文。',
});
return false;
}
AjaxForm(
"GET",
"/api/tool/hashids/decode/" + encodeValue,
"",
function () {
$(this).hide();
$("#btnDecodeLoading").show();
},
function (data) {
$("#btnDecodeLoading").hide();
$("#btnDecode").show();
$("#DecodeValueToNumber").val(data.val)
},
function (response) {
$("#btnDecodeLoading").hide();
$("#btnDecode").show();
AjaxError(response);
}
);
});
})
</script>
</body>
</html>
+104
View File
@@ -0,0 +1,104 @@
<!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/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="alert alert-info alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<p>推荐使用: <code>ELK 组件</code> ,本功能仅仅是读取文本进行展示。</p>
</div>
<div class="card">
<header class="card-header">
<div class="card-title">日志列表 <code>仅展示最新的 100 条日志。</code></div>
<ul class="card-actions">
<li>
<a href="#!" onclick="location.reload();" data-toggle="tooltip" title="" data-original-title="刷新"><i class="mdi mdi-refresh"></i></a>
</li>
</ul>
</header>
<div class="card-body">
<div class="accordion">
{{range $key, $value := .Logs}}
{{$badgeLevelClass := ""}}
{{$badgeMethodClass := ""}}
{{$badgeCodeClass := ""}}
<div class="card">
<div class="card-header">
<div class="card-title">
{{if eq $value.Level "info"}}
{{$badgeLevelClass = "badge-info"}}
{{else if eq $value.Level "error"}}
{{$badgeLevelClass = "badge-danger"}}
{{else if eq $value.Level "warn"}}
{{$badgeLevelClass = "badge-warning"}}
{{else}}
{{$badgeLevelClass = "badge-dark"}}
{{end}}
{{if eq $value.Method "GET"}}
{{$badgeMethodClass = "badge-primary"}}
{{else if eq $value.Method "POST"}}
{{$badgeMethodClass = "badge-success"}}
{{else if eq $value.Method "DELETE"}}
{{$badgeMethodClass = "badge-danger"}}
{{else if eq $value.Method "PUT"}}
{{$badgeMethodClass = "badge-yellow"}}
{{else if eq $value.Method "PATCH"}}
{{$badgeMethodClass = "badge-cyan"}}
{{else}}
{{$badgeMethodClass = "badge-dark"}}
{{end}}
{{if eq $value.HTTPCode 200}}
{{$badgeCodeClass = "badge-success"}}
{{else}}
{{$badgeCodeClass = "badge-dark"}}
{{end}}
{{if eq $value.HTTPCode 0}}
<a data-toggle="collapse" data-target="#collapse{{$key}}" aria-expanded="false" href="#!" class="collapsed">
<span class="badge {{$badgeLevelClass}}">{{$value.Level}}</span>
<span class="badge badge-brown">{{$value.Time}}</span>
<code>{{$value.Msg}}</code>
</a>
{{else}}
<a data-toggle="collapse" data-target="#collapse{{$key}}" aria-expanded="false" href="#!" class="collapsed">
<span class="badge {{$badgeLevelClass}}">{{$value.Level}}</span>
<span class="badge badge-brown">{{$value.Time}}</span>
<span class="badge {{$badgeMethodClass}}">{{$value.Method}}</span>
<span class="badge {{$badgeCodeClass}}">{{$value.HTTPCode}}</span>
<span class="badge badge-muted">{{$value.TraceID}}</span>
<span class="badge badge-purple-light">{{$value.CostSeconds}} s</span>
<code>{{$value.Path}}</code>
</a>
{{end}}
</div>
</div>
<div id="collapse{{$key}}" class="collapse">
<div class="card-body">
<pre>{{$value.Content}}</pre>
</div>
</div>
</div>
{{end}}
</div>
</div>
</div>
</div>
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
+11 -2
View File
@@ -1,6 +1,15 @@
## 执行命令
1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割
1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`
在根目录下执行脚本:`./scripts/gormgen.sh addr user pass name tables`
- addr:数据库地址,例如:127.0.0.1:3306
- user:账号,例如:root
- pass:密码,例如:root
- name:数据库名称,例如:go_gin_api
- tables:表名,默认为 *,多个表名可用“,”分割,例如:user_demo
例如:
```
./scripts/gormgen.sh 127.0.0.1:3306 root root go_gin_api user_demo
```
## 参考
- https://github.com/MohamedBassem/gormgen
+2
View File
@@ -3,6 +3,7 @@ package pkg
import (
"bytes"
"errors"
"fmt"
"go/format"
"io/ioutil"
"log"
@@ -119,6 +120,7 @@ func (g *Generator) Flush() error {
if err := ioutil.WriteFile(filename, g.buf[k].Bytes(), 0777); err != nil {
log.Fatalln(err)
}
fmt.Println(" └── file : ", fmt.Sprintf("%s_repo/gen_%s.go", strings.ToLower(k), strings.ToLower(k)))
}
return nil
}
-2
View File
@@ -78,7 +78,6 @@ func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
optionField fieldConfig
)
// type is ident, get onlyField type
if t, _ok := v.Type.(*ast.Ident); _ok {
optionField.FieldType = t.String()
} else {
@@ -89,7 +88,6 @@ func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
}
}
// get file name
if len(v.Names) > 0 {
optionField.FieldName = v.Names[0].String()
optionField.ColumnName = gorm.ToDBName(optionField.FieldName)
+23 -1
View File
@@ -52,7 +52,7 @@ func (t *{{.StructName}}) Delete(db *gorm.DB) (err error) {
}
func (t *{{.StructName}}) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
if err = db.Model(&{{.StructName}}{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
@@ -136,6 +136,28 @@ func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}(p d
return qb
}
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}In(value []{{.FieldType}}) *{{$queryBuilderName}} {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", "IN"),
value,
})
return qb
}
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}NotIn(value []{{.FieldType}}) *{{$queryBuilderName}} {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", "NOT IN"),
value,
})
return qb
}
func (qb *{{$queryBuilderName}}) OrderBy{{call $.Helpers.Titelize .FieldName}}(asc bool) *{{$queryBuilderName}} {
order := "DESC"
if asc {
+96
View File
@@ -3,4 +3,100 @@
```$xslt
// test_handler 为 ./internal/api/controller/ 中的包名
./scripts/handlergen.sh test_handler
```
## 模板文件参考
```go
package test_handler
import (
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// Create 创建用户
// @Tags Test
// @Router /test/create [post]
Create() core.HandlerFunc
// Update 编辑用户
// @Tags Test
// @Router /test/update [post]
Update() core.HandlerFunc
// Delete 删除用户
// @Tags Test
// @Router /test/delete [post]
Delete() core.HandlerFunc
// Detail 用户详情
// @Tags Test
// @Router /test/detail [post]
Detail() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
func (h *handler) i() {}
```
以上会生成 4 个文件
- func_create.go
- func_update.go
- func_delete.go
- func_detail.go
## func_create.go 参考
```go
package test_handler
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type createRequest struct{}
type createResponse struct{}
// Create 创建用户
// @Summary 创建用户
// @Description 创建用户
// @Tags Test
// @Accept json
// @Produce json
// @Param Request body createRequest true "请求信息"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /test/create [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
}
}
```
+34 -10
View File
@@ -3,13 +3,15 @@ package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"os"
"strings"
"unicode"
"github.com/dave/dst"
"github.com/dave/dst/decorator"
)
var handlerName string
@@ -23,26 +25,31 @@ func init() {
func main() {
fs := token.NewFileSet()
file := fmt.Sprintf("./internal/api/controller/%s/handler.go", handlerName)
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
filePath := fmt.Sprintf("./internal/api/controller/%s", handlerName)
parsedFile, err := decorator.ParseFile(fs, filePath+"/handler.go", nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", file, err)
log.Fatalf("parsing package: %s: %s\n", filePath, err)
}
ast.Inspect(parsedFile, func(n ast.Node) bool {
decl, ok := n.(*ast.GenDecl)
files, _ := ioutil.ReadDir(filePath)
if len(files) > 1 {
log.Fatalf("请先确保 %s 目录中,有且仅有 handler.go 一个文件。", filePath)
}
dst.Inspect(parsedFile, func(n dst.Node) bool {
decl, ok := n.(*dst.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
typeSpec, _ok := spec.(*ast.TypeSpec)
typeSpec, _ok := spec.(*dst.TypeSpec)
if !_ok {
continue
}
var interfaceType *ast.InterfaceType
if interfaceType, ok = typeSpec.Type.(*ast.InterfaceType); !ok {
var interfaceType *dst.InterfaceType
if interfaceType, ok = typeSpec.Type.(*dst.InterfaceType); !ok {
continue
}
@@ -58,6 +65,7 @@ func main() {
if err != nil {
fmt.Printf("create and open func file error %v\n", err.Error())
}
fmt.Println(" └── file : ", filename)
funcContent := fmt.Sprintf("package %s\n\n", handlerName)
funcContent += "import (\n"
@@ -65,6 +73,22 @@ func main() {
funcContent += "\n)\n\n"
funcContent += fmt.Sprintf("\n\ntype %sRequest struct {}\n\n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("type %sResponse struct {}\n\n", Lcfirst(v.Names[0].String()))
// 首行注释
funcContent += fmt.Sprintf("%s\n", v.Decorations().Start.All()[0])
nameArr := strings.Split(v.Decorations().Start.All()[0], v.Names[0].String())
funcContent += fmt.Sprintf("// @Summary%s \n", nameArr[1])
funcContent += fmt.Sprintf("// @Description%s \n", nameArr[1])
// Tags
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[1])
funcContent += fmt.Sprintf("// @Accept json \n")
funcContent += fmt.Sprintf("// @Produce json \n")
funcContent += fmt.Sprintf("// @Param Request body %sRequest true \"请求信息\" \n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("// @Success 200 {object} %sResponse \n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("// @Failure 400 {object} code.Failure \n")
// Router
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[2])
funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(c core.Context) {\n\n}}", v.Names[0].String())
funcFile.WriteString(funcContent)
+11
View File
@@ -0,0 +1,11 @@
## 执行命令
在根目录下执行脚本:`./scripts/init.sh addr user pass name`
- addr:数据库地址,例如:127.0.0.1:3306
- user:账号,例如:root
- pass:密码,例如:root
- name:数据库名称,例如:go_gin_api
例如:
```
./scripts/init.sh 127.0.0.1:3306 root root go_gin_api
```
+113
View File
@@ -0,0 +1,113 @@
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()
}
+63
View File
@@ -0,0 +1,63 @@
package mysql
import (
"fmt"
"github.com/pkg/errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
var _ Repo = (*dbRepo)(nil)
type Repo interface {
i()
GetDb() *gorm.DB
DbClose() error
}
type dbRepo struct {
DbConn *gorm.DB
}
func New(dbAddr, dbUser, dbPass, dbName string) (Repo, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
dbUser,
dbPass,
dbAddr,
dbName,
true,
"Local")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
})
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName))
}
db.Set("gorm:table_options", "CHARSET=utf8mb4")
return &dbRepo{
DbConn: db,
}, nil
}
func (d *dbRepo) i() {}
func (d *dbRepo) GetDb() *gorm.DB {
return d.DbConn
}
func (d *dbRepo) DbClose() error {
sqlDB, err := d.DbConn.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
+44
View File
@@ -0,0 +1,44 @@
package mysql
//CREATE TABLE `admin` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
//`username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
//`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
//`nickname` varchar(60) NOT NULL DEFAULT '' COMMENT '昵称',
//`mobile` varchar(20) NOT NULL DEFAULT '' 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 '创建时间',
//`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
//`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',
//PRIMARY KEY (`id`),
//UNIQUE KEY `unique_username` (`username`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';
func CreateAdminTableSql() (sql string) {
sql = "CREATE TABLE `admin` ("
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
sql += "`username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',"
sql += "`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',"
sql += "`nickname` varchar(60) NOT NULL DEFAULT '' COMMENT '昵称',"
sql += "`mobile` varchar(20) NOT NULL DEFAULT '' 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 '创建时间',"
sql += "`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',"
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 += "UNIQUE KEY `unique_username` (`username`)"
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';"
return
}
func CreateAdminTableDataSql() (sql string) {
sql = "INSERT INTO `admin` (`id`, `username`, `password`, `nickname`, `mobile`, `created_user`) VALUES"
sql += "(1, 'admin', 'f78382de80cf583cf854bbac0b6e796fbde36fe2739ca4ae072637010f179cb0', '管理员', '13888888888', 'init');"
return
}
+43
View File
@@ -0,0 +1,43 @@
package mysql
//CREATE TABLE `authorized` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
//`business_key` varchar(32) NOT NULL DEFAULT '' COMMENT '调用方key',
//`business_secret` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方secret',
//`business_developer` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方对接人',
//`remark` varchar(255) NOT NULL DEFAULT '' 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 '创建时间',
//`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
//`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',
//PRIMARY KEY (`id`),
//UNIQUE KEY `unique_business_key` (`business_key`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权的调用方表';
func CreateAuthorizedTableSql() (sql string) {
sql = "CREATE TABLE `authorized` ("
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
sql += "`business_key` varchar(32) NOT NULL DEFAULT '' COMMENT '调用方key',"
sql += "`business_secret` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方secret',"
sql += "`business_developer` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方对接人',"
sql += "`remark` varchar(255) NOT NULL DEFAULT '' 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 '创建时间',"
sql += "`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',"
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 += "UNIQUE KEY `unique_business_key` (`business_key`)"
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权的调用方表';"
return
}
func CreateAuthorizedTableDataSql() (sql string) {
sql = "INSERT INTO `authorized` (`id`, `business_key`, `business_secret`, `business_developer`, `remark`, `created_user`) VALUES (1, 'admin', '12878dd962115106db6d', '管理员', '管理面板调用', 'init');"
return
}
+42
View File
@@ -0,0 +1,42 @@
package mysql
//CREATE TABLE `authorized_api` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
//`business_key` varchar(30) NOT NULL DEFAULT '' COMMENT '调用方key',
//`method` varchar(30) NOT NULL DEFAULT '' COMMENT '请求方式',
//`api` varchar(100) NOT NULL DEFAULT '' COMMENT '请求地址',
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
//`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',
//`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='已授权接口地址';
func CreateAuthorizedAPITableSql() (sql string) {
sql = "CREATE TABLE `authorized_api` ("
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
sql += "`business_key` varchar(30) NOT NULL DEFAULT '' COMMENT '调用方key',"
sql += "`method` varchar(30) NOT NULL DEFAULT '' COMMENT '请求方式',"
sql += "`api` varchar(100) NOT NULL DEFAULT '' COMMENT '请求地址',"
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
sql += "`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',"
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='已授权的调用方表';"
return
}
func CreateAuthorizedAPITableDataSql() (sql string) {
sql = "INSERT INTO `authorized_api` (`id`, `business_key`, `method`, `api`,`created_user`) VALUES"
sql += "(1, 'admin', 'GET', '/api/**', 'init'),"
sql += "(2, 'admin', 'POST', '/api/**', 'init'),"
sql += "(3, 'admin', 'PUT', '/api/**', 'init'),"
sql += "(4, 'admin', 'DELETE', '/api/**', 'init'),"
sql += "(5, 'admin', 'PATCH', '/api/**', 'init');"
return
}
+27
View File
@@ -0,0 +1,27 @@
package mysql
//CREATE TABLE `user_demo` (
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
//`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
//`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',
//`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
//PRIMARY KEY (`id`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';
func CreateUserDemoTableSql() (sql string) {
sql = "CREATE TABLE `user_demo` ("
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
sql += "`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',"
sql += "`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',"
sql += "`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',"
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',"
sql += "PRIMARY KEY (`id`)"
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';"
return
}
+195
View File
@@ -0,0 +1,195 @@
package main
import (
"bufio"
"bytes"
"crypto/sha256"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/koketama/errors"
"go.uber.org/zap"
"golang.org/x/tools/go/packages"
)
var stdlib = make(map[string]bool)
func init() {
pkgs, err := packages.Load(nil, "std")
if err != nil {
log.Fatal("get go stdlib err", zap.Error(err))
}
for _, pkg := range pkgs {
if !strings.HasPrefix(pkg.ID, "vendor") {
stdlib[pkg.ID] = true
}
}
}
var module string
func init() {
file, err := os.Open("./go.mod")
if err != nil {
log.Fatal("no go.mod file found", zap.Error(err))
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "module ") {
module = strings.TrimSpace(line[7:])
break
}
}
if module == "" {
log.Fatal("go.mod illegal")
}
}
func main() {
err := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || strings.HasPrefix(path, ".") || strings.HasPrefix(path, "vendor") || strings.HasSuffix(path, ".pb.go") || filepath.Ext(path) != ".go" {
return nil
}
raw, err := ioutil.ReadFile(path)
if err != nil {
return errors.Wrapf(err, "read file %s err", path)
}
digest0 := sha256.Sum256(raw)
if raw, err = format.Source(raw); err != nil {
return errors.Wrapf(err, "format file %s err", path)
}
file, err := parser.ParseFile(token.NewFileSet(), "", raw, 0)
if err != nil {
return errors.Wrapf(err, "parse file %s err", path)
}
var first, last int
var imports []*ast.ImportSpec
comments := make(map[string]string)
ast.Inspect(file, func(n ast.Node) bool {
switch spec := n.(type) {
case *ast.ImportSpec:
if first == 0 {
first = int(spec.Pos())
}
last = int(spec.End())
imports = append(imports, spec)
k := last - 1
for ; k < len(raw); k++ {
if raw[k] == '\r' || raw[k] == '\n' {
break
}
}
comment := string(raw[last-1 : k])
if index := strings.Index(comment, "//"); index != -1 {
comments[spec.Path.Value] = strings.TrimSpace(comment[index+2:])
}
}
return true
})
if imports != nil {
buf := bytes.NewBuffer(nil)
buf.Write(raw[:first-2])
buf.WriteString(sort(imports, comments))
buf.Write(raw[last-1:])
if raw, err = format.Source(buf.Bytes()); err != nil {
return errors.Wrapf(err, "double format file %s err", path)
}
}
digest1 := sha256.Sum256(raw)
if !bytes.Equal(digest0[:], digest1[:]) {
fmt.Println(path)
}
if err = ioutil.WriteFile(path, raw, info.Mode()); err != nil {
return errors.Wrapf(err, "write file %s err", path)
}
return nil
})
if err != nil {
log.Fatal("scan project err", zap.Error(err))
}
}
func sort(imports []*ast.ImportSpec, comments map[string]string) string {
system := bytes.NewBuffer(nil)
group := bytes.NewBuffer(nil)
others := bytes.NewBuffer(nil)
for _, pkg := range imports {
value := strings.Trim(pkg.Path.Value, `"`)
switch {
case stdlib[value]:
if pkg.Name != nil {
system.WriteString(pkg.Name.String())
system.WriteString(" ")
}
system.WriteString(pkg.Path.Value)
if comment, ok := comments[pkg.Path.Value]; ok {
system.WriteString(" ")
system.WriteString("// ")
system.WriteString(comment)
}
system.WriteString("\n")
case strings.HasPrefix(value, module):
if pkg.Name != nil {
group.WriteString(pkg.Name.String())
group.WriteString(" ")
}
group.WriteString(pkg.Path.Value)
if comment, ok := comments[pkg.Path.Value]; ok {
group.WriteString(" ")
group.WriteString("// ")
group.WriteString(comment)
}
group.WriteString("\n")
default:
if pkg.Name != nil {
others.WriteString(pkg.Name.String())
others.WriteString(" ")
}
others.WriteString(pkg.Path.Value)
if comment, ok := comments[pkg.Path.Value]; ok {
others.WriteString(" ")
others.WriteString("// ")
others.WriteString(comment)
}
others.WriteString("\n")
}
}
return fmt.Sprintf("%s\n%s\n%s", system.String(), group.String(), others.String())
}
+39 -31
View File
@@ -2,17 +2,15 @@ package main
import (
"database/sql"
"flag"
"fmt"
"log"
"os"
"regexp"
"strings"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/logger"
"github.com/xinliangnote/go-gin-api/cmd/mysqlmd/mysql"
"go.uber.org/zap"
"gorm.io/gorm"
)
@@ -33,39 +31,46 @@ type tableColumn struct {
ColumnDefault sql.NullString `db:"COLUMN_DEFAULT"` // default value
}
func main() {
// 初始化 logger
loggers, err := logger.NewJSONLogger(
logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName(), env.Active().Value())),
logger.WithTimeLayout("2006-01-02 15:04:05"),
logger.WithFileP(configs.ProjectLogFile()),
)
if err != nil {
panic(err)
}
defer loggers.Sync()
var (
dbAddr string
dbUser string
dbPass string
dbName string
genTables string
)
func init() {
addr := flag.String("addr", "", "请输入 db 地址,例如:127.0.0.1:3306\n")
user := flag.String("user", "", "请输入 db 用户名\n")
pass := flag.String("pass", "", "请输入 db 密码\n")
name := flag.String("name", "", "请输入 db 名称\n")
table := flag.String("tables", "*", "请输入 table 名称,默认为“*”,多个可用“,”分割\n")
flag.Parse()
dbAddr = *addr
dbUser = *user
dbPass = *pass
dbName = strings.ToLower(*name)
genTables = strings.ToLower(*table)
}
func main() {
// 初始化 DB
dbRepo, err := db.New()
db, err := mysql.New(dbAddr, dbUser, dbPass, dbName)
if err != nil {
loggers.Fatal("new db err", zap.Error(err))
log.Fatal("new db err", err)
}
defer func() {
if err := dbRepo.DbWClose(); err != nil {
loggers.Error("dbw close err", zap.Error(err))
}
if err := dbRepo.DbRClose(); err != nil {
loggers.Error("dbr close err", zap.Error(err))
if err := db.DbClose(); err != nil {
log.Println("db close err", err)
}
}()
dbName := configs.Get().MySQL.Read.Name
genTables := configs.Get().Cmd.GenTables
tables, err := queryTables(dbRepo.GetDbR(), dbName, genTables)
tables, err := queryTables(db.GetDb(), dbName, genTables)
if err != nil {
loggers.Error("query tables of database err", zap.Error(err))
log.Println("query tables of database err", err)
return
}
@@ -73,13 +78,15 @@ func main() {
filepath := "./internal/api/repository/db_repo/" + table.Name + "_repo"
_ = os.Mkdir(filepath, 0766)
fmt.Println("create dir : ", filepath)
mdName := fmt.Sprintf("%s/gen_table.md", filepath)
mdFile, err := os.OpenFile(mdName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open markdown file error %v\n", err.Error())
fmt.Printf("markdown file error %v\n", err.Error())
return
}
fmt.Println(" └── file : ", table.Name+"_repo/gen_table.md")
modelName := fmt.Sprintf("%s/gen_model.go", filepath)
modelFile, err := os.OpenFile(modelName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
@@ -87,6 +94,7 @@ func main() {
fmt.Printf("create and open model file error %v\n", err.Error())
return
}
fmt.Println(" └── file : ", table.Name+"_repo/gen_model.go")
modelContent := fmt.Sprintf("package %s%s\n", table.Name, "_repo")
modelContent += fmt.Sprintf(`import "time"`)
@@ -102,7 +110,7 @@ func main() {
"| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |\n" +
"| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n"
columnInfo, columnInfoErr := queryTableColumn(dbRepo.GetDbR(), dbName, table.Name)
columnInfo, columnInfoErr := queryTableColumn(db.GetDb(), dbName, table.Name)
if columnInfoErr != nil {
continue
}
@@ -163,7 +171,7 @@ func queryTables(db *gorm.DB, dbName string, tableName string) ([]tableInfo, err
}
// filter tables when specified tables params
if tableName != "" {
if tableName != "*" {
tableCollect = nil
chooseTables := strings.Split(tableName, ",")
indexMap := make(map[int]int)
+63
View File
@@ -0,0 +1,63 @@
package mysql
import (
"fmt"
"github.com/pkg/errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
var _ Repo = (*dbRepo)(nil)
type Repo interface {
i()
GetDb() *gorm.DB
DbClose() error
}
type dbRepo struct {
DbConn *gorm.DB
}
func New(dbAddr, dbUser, dbPass, dbName string) (Repo, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
dbUser,
dbPass,
dbAddr,
dbName,
true,
"Local")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
})
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName))
}
db.Set("gorm:table_options", "CHARSET=utf8mb4")
return &dbRepo{
DbConn: db,
}, nil
}
func (d *dbRepo) i() {}
func (d *dbRepo) GetDb() *gorm.DB {
return d.DbConn
}
func (d *dbRepo) DbClose() error {
sqlDB, err := d.DbConn.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
+12 -12
View File
@@ -54,19 +54,15 @@ type Config struct {
ExpireDuration time.Duration `toml:"expireDuration"`
} `toml:"jwt"`
Aes struct {
Key string `toml:"key"`
Iv string `toml:"iv"`
} `toml:"aes"`
URLToken struct {
Secret string `toml:"secret"`
ExpireDuration time.Duration `toml:"expireDuration"`
} `toml:"urlToken"`
Rsa struct {
Private string `toml:"private"`
Public string `toml:"public"`
} `toml:"rsa"`
Cmd struct {
GenTables string `toml:"genTables"`
} `toml:"cmd"`
HashIds struct {
Secret string `toml:"secret"`
Length int `toml:"length"`
} `toml:"hashids"`
}
func init() {
@@ -98,3 +94,7 @@ func ProjectPort() string {
func ProjectLogFile() string {
return fmt.Sprintf("./logs/%s-access.log", ProjectName())
}
func InitDBLockFile() string {
return "cmd/init/db/init_db.lock"
}
+6 -44
View File
@@ -34,48 +34,10 @@ to = "" # 收件人邮箱,多个可以逗号(,)分割
secret = 'i1ydX9RtHyuJTrw7frcu' # JWT secret
expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时)
[aes]
key = 'IgkibX71IEf382PT'
iv = 'IgkibX71IEf382PT'
[urlToken]
secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)
[rsa]
private = '-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA1O3p0JN0/RrP7eY3f81izPf16FS0WMNGCJkd+y5c6yBzUvN0
IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNom/UCAzAYEgdIsNee5LSN/7e0T2/Q
vsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT34GGRdrSJ+m4iVk/Kt95tavBNk+ND
VSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e9Tn8f99M2VMqpzXHXZzv1ABmqufz
S20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HMxLYEFN9h2MWYgxLm9Z0rLMrWwMM+
E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbzowIDAQABAoIBAQCO1RE1ItUlO6kj
Un0ENAgEqojAUqGvsT33Yo7kAZO+/cOeb0UEqk0iq5bf7L9ncBynWDg6ZPc6X3/g
wdFdKxAvHck9zjM3VL+EMP+bNyrR0K8ZYk5Kx+Q/PEK+Mp8dfRdgggAUsZaNWB+a
rVVspiMo1wo28KBl5x8NevTnJkOLqXAyB7UyLWqnOL1fb988lZvZPR7ZUYroVIZa
pyXtZcafIJeKyQ3bvWI5+eFqOe61Z4Bx1+TpfZ3fKfSDW0vhxzNqaimOa8jSXtMJ
jMeOctL4nZ0TPo/jS3I+XlaH4ZQlFLuUWGscpxwfEeBN23I8HRLkZXJsw66yvRN3
s4bUKPXRAoGBAP/3oSZAECvfsYYzs76tnrAmR/0GxCqgguxDlWn5DowQzdWFOdHC
ZbTo/hUVoMSQnO1EKCFlnBS+wg/3TuIzUO0ewC1aeT7qHbOMDl0zKbNpS2Z9/j+U
zro+qz7XmkWolMCfmDrCrw9CtCxcMSII+ajbI8SAgFVMz9XnDt+xW9E9AoGBANT0
4F6kCUJTEyqf2+v84tjQ2wGIF6XtZPU9JR806zeMyahQ9F6z3hY8BYb0tIy5b3uJ
VlJ9TG1qg/t59TWxIq43mYSUJHe0aJi3ilooObQtHlhPu8nwmmX47sX0PyG2hMoD
kBVxTpTDmBaDz7O9uBnlMXJN5qEygctaixpEbmZfAoGBAMBA9kEMjRjnAyeRXcgy
D6aumhNqKZz6wltCx864yjxZwsBFOJBcOpgPCAg+HmqFU9jCAIJVF05dmNT1I8Ky
WG5BUoa+FaMzpOtenstRylh/Far9pyGKW1t4BpdEyRLY9CFZvbUk1OfZagqHlD/E
DgDN16eX/MwUzWYUDg/l3tjhAoGBAKGip/ZNjVWRFpggs9z/mfK1O7WC5Wgksp9N
ZLK2CN6l9p3RrFmBLk00C4HulGfHi+15RVLhFbRqx3iFje/N3iPbwaMWikNtZIKd
tN5Pb9To9gJTqpZRD+/cLOeFRrHBBjMK1z7fPKS/fN2B+JFVq7nD827t3+J0In4F
4FT0odMDAoGBAJk3ELB/FHY8xzZ4jF1wG/a1CK681Xm6SuU5KIELDSAUNoou6OPG
mS8gU20MMPAeV2z7khyDcSxlHsUyL73eLeaakbQov9NMW7cc99XX4wnP4W7FRpmr
QbHmKuHIRFHCFv+XX8c0aK2mDZMUlzJdy4FgD/YCEZ7kZMZKyvZW/ZuV
-----END RSA PRIVATE KEY-----'
public = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1O3p0JN0/RrP7eY3f81i
zPf16FS0WMNGCJkd+y5c6yBzUvN0IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNo
m/UCAzAYEgdIsNee5LSN/7e0T2/QvsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT3
4GGRdrSJ+m4iVk/Kt95tavBNk+NDVSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e
9Tn8f99M2VMqpzXHXZzv1ABmqufzS20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HM
xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz
owIDAQAB
-----END PUBLIC KEY-----'
[cmd]
genTables = 'user_demo'
[hashids]
secret = '6ab6122836cfef95f8db' # hashids secret
length = 12 # hashids length
+1071
View File
File diff suppressed because it is too large Load Diff
+1071
View File
File diff suppressed because it is too large Load Diff
+718
View File
@@ -1,4 +1,217 @@
definitions:
admin_handler.createResponse:
properties:
id:
description: 主键ID
type: integer
type: object
admin_handler.deleteResponse:
properties:
id:
description: 主键ID
type: integer
type: object
admin_handler.detailResponse:
properties:
mobile:
description: 手机号
type: string
nickname:
description: 昵称
type: string
username:
description: 用户名
type: string
type: object
admin_handler.listData:
properties:
created_at:
description: 创建时间
type: string
created_user:
description: 创建人
type: string
hashid:
description: hashid
type: string
id:
description: ID
type: integer
is_used:
description: 是否启用 1:是 -1:否
type: integer
mobile:
description: 手机号
type: string
nickname:
description: 昵称
type: string
updated_at:
description: 更新时间
type: string
updated_user:
description: 更新人
type: string
username:
description: 用户名
type: string
type: object
admin_handler.listResponse:
properties:
list:
items:
$ref: '#/definitions/admin_handler.listData'
type: array
pagination:
properties:
current_page:
type: integer
pre_page_count:
type: integer
total:
type: integer
type: object
type: object
admin_handler.loginResponse:
properties:
token:
description: 用户身份标识
type: string
type: object
admin_handler.logoutResponse:
properties:
username:
description: 用户账号
type: string
type: object
admin_handler.modifyPasswordResponse:
properties:
username:
description: 用户账号
type: string
type: object
admin_handler.modifyPersonalInfoResponse:
properties:
username:
description: 用户账号
type: string
type: object
admin_handler.resetPasswordResponse:
properties:
id:
description: 主键ID
type: integer
type: object
admin_handler.updateUsedResponse:
properties:
id:
description: 主键ID
type: integer
type: object
authorized_handler.createAPIResponse:
properties:
id:
description: 主键ID
type: integer
type: object
authorized_handler.createResponse:
properties:
id:
description: 主键ID
type: integer
type: object
authorized_handler.deleteAPIResponse:
properties:
id:
description: 主键ID
type: integer
type: object
authorized_handler.deleteResponse:
properties:
id:
description: 主键ID
type: integer
type: object
authorized_handler.listAPIData:
properties:
api:
description: 调用方对接人
type: string
business_key:
description: 调用方key
type: string
hash_id:
description: hashID
type: string
method:
description: 调用方secret
type: string
type: object
authorized_handler.listAPIResponse:
properties:
list:
items:
$ref: '#/definitions/authorized_handler.listAPIData'
type: array
type: object
authorized_handler.listData:
properties:
business_developer:
description: 调用方对接人
type: string
business_key:
description: 调用方key
type: string
business_secret:
description: 调用方secret
type: string
created_at:
description: 创建时间
type: string
created_user:
description: 创建人
type: string
hashid:
description: hashid
type: string
id:
description: ID
type: integer
is_used:
description: 是否启用 1:是 -1:否
type: integer
remark:
description: 备注
type: string
updated_at:
description: 更新时间
type: string
updated_user:
description: 更新人
type: string
type: object
authorized_handler.listResponse:
properties:
list:
items:
$ref: '#/definitions/authorized_handler.listData'
type: array
pagination:
properties:
current_page:
type: integer
pre_page_count:
type: integer
total:
type: integer
type: object
type: object
authorized_handler.updateUsedResponse:
properties:
id:
description: 主键ID
type: integer
type: object
code.Failure:
properties:
code:
@@ -17,6 +230,18 @@ definitions:
description: 过期时间
type: integer
type: object
tool_handler.hashIdsDecodeResponse:
properties:
val:
description: 解密后的值
type: integer
type: object
tool_handler.hashIdsEncodeResponse:
properties:
val:
description: 加密后的值
type: string
type: object
user_handler.createRequest:
properties:
mobile:
@@ -80,6 +305,499 @@ info:
title: swagger 接口文档
version: "2.0"
paths:
/api/admin:
get:
consumes:
- application/json
description: 管理员列表
parameters:
- description: 第几页
in: query
name: page
type: integer
- description: 每页显示条数
in: query
name: page_size
type: string
- description: 用户名
in: query
name: username
type: string
- description: 昵称
in: query
name: nickname
type: string
- description: 手机号
in: query
name: mobile
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.listResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 管理员列表
tags:
- API.admin
post:
consumes:
- multipart/form-data
description: 新增管理员
parameters:
- description: 用户名
in: formData
name: username
required: true
type: string
- description: 昵称
in: formData
name: nickname
required: true
type: string
- description: 手机号
in: formData
name: mobile
required: true
type: string
- description: 密码
in: formData
name: password
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.createResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 新增管理员
tags:
- API.admin
/api/admin/{id}:
delete:
consumes:
- application/json
description: 删除管理员
parameters:
- description: hashId
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.deleteResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 删除管理员
tags:
- API.admin
/api/admin/info:
get:
consumes:
- application/json
description: 管理员详情
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.detailResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 管理员详情
tags:
- API.admin
/api/admin/login:
post:
consumes:
- application/json
description: 管理员登出
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.logoutResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 管理员登出
tags:
- API.admin
/api/admin/modify_password:
patch:
consumes:
- multipart/form-data
description: 修改个人信息
parameters:
- description: 昵称
in: formData
name: nickname
required: true
type: string
- description: 手机号
in: formData
name: mobile
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.modifyPersonalInfoResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 修改个人信息
tags:
- API.admin
/api/admin/reset_password/{id}:
patch:
consumes:
- application/json
description: 重置密码
parameters:
- description: hashId
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.resetPasswordResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 重置密码
tags:
- API.admin
/api/admin/used:
patch:
consumes:
- multipart/form-data
description: 更新管理员为启用/禁用
parameters:
- description: Hashid
in: formData
name: id
required: true
type: string
- description: 是否启用 1:是 -1:否
in: formData
name: used
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin_handler.updateUsedResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 更新管理员为启用/禁用
tags:
- API.admin
/api/authorized:
get:
consumes:
- application/json
description: 调用方列表
parameters:
- description: 第几页
in: query
name: page
type: integer
- description: 每页显示条数
in: query
name: page_size
type: string
- description: 调用方key
in: query
name: business_key
type: string
- description: 调用方secret
in: query
name: business_secret
type: string
- description: 调用方对接人
in: query
name: business_developer
type: string
- description: 备注
in: path
name: remark
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/authorized_handler.listResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 调用方列表
tags:
- API.authorized
post:
consumes:
- multipart/form-data
description: 新增调用方
parameters:
- description: 调用方key
in: formData
name: business_key
required: true
type: string
- description: 调用方对接人
in: formData
name: business_developer
required: true
type: string
- description: 备注
in: formData
name: remark
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/authorized_handler.createResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 新增调用方
tags:
- API.authorized
/api/authorized/{id}:
delete:
consumes:
- application/json
description: 删除调用方
parameters:
- description: hashId
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/authorized_handler.deleteResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 删除调用方
tags:
- API.authorized
/api/authorized/used:
patch:
consumes:
- multipart/form-data
description: 更新调用方为启用/禁用
parameters:
- description: Hashid
in: formData
name: id
required: true
type: string
- description: 是否启用 1:是 -1:否
in: formData
name: used
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/authorized_handler.updateUsedResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 更新调用方为启用/禁用
tags:
- API.authorized
/api/authorized_api:
get:
consumes:
- application/json
description: 调用方接口地址列表
parameters:
- description: 调用方key
in: query
name: business_key
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/authorized_handler.listAPIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 调用方接口地址列表
tags:
- API.authorized
post:
consumes:
- multipart/form-data
description: 授权调用方接口地址
parameters:
- description: HashID
in: formData
name: id
required: true
type: string
- description: 请求方法
in: formData
name: method
required: true
type: string
- description: 请求地址
in: formData
name: api
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/authorized_handler.createAPIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 授权调用方接口地址
tags:
- API.authorized
/api/authorized_api/{id}:
delete:
consumes:
- application/json
description: 删除调用方接口地址
parameters:
- description: 主键ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/authorized_handler.deleteAPIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 删除调用方接口地址
tags:
- API.authorized
/api/tool/hashids/decode/{id}:
get:
consumes:
- application/json
description: HashIds 解密
parameters:
- description: 需解密的密文
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/tool_handler.hashIdsDecodeResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: HashIds 解密
tags:
- API.tool
/api/tool/hashids/encode/{id}:
get:
consumes:
- application/json
description: HashIds 加密
parameters:
- description: 需加密的数字
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/tool_handler.hashIdsEncodeResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: HashIds 加密
tags:
- API.tool
/auth/get:
post:
consumes:
+9 -2
View File
@@ -4,32 +4,39 @@ go 1.15
require (
github.com/99designs/gqlgen v0.13.0
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/dave/dst v0.26.2
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/pprof v1.2.1
github.com/gin-gonic/gin v1.6.3
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/go-openapi/spec v0.20.0 // indirect
github.com/go-redis/redis/v7 v7.4.0
github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.5.4 // indirect
github.com/jinzhu/gorm v1.9.16
github.com/koketama/errors v1.0.2
github.com/koketama/urltable v0.1.3
github.com/onsi/ginkgo v1.14.2 // indirect
github.com/onsi/gomega v1.10.4 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v0.9.3
github.com/rs/cors v1.7.0
github.com/shirou/gopsutil v3.21.2+incompatible
github.com/speps/go-hashids v2.0.0+incompatible
github.com/spf13/cast v1.3.0
github.com/spf13/viper v1.7.1
github.com/swaggo/gin-swagger v1.3.0
github.com/swaggo/swag v1.7.0
github.com/tklauser/go-sysconf v0.3.4 // indirect
github.com/vektah/gqlparser/v2 v2.1.0
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.16.0
golang.org/x/mod v0.4.0 // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a // indirect
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a
google.golang.org/grpc v1.27.0
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+37 -2
View File
@@ -25,6 +25,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
@@ -57,6 +59,12 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/dst v0.26.2 h1:lnxLAKI3tx7MgLNVDirFCsDTlTG9nKTk7GcptKcWSwY=
github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU=
github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8=
github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -95,6 +103,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
@@ -163,6 +173,7 @@ github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@@ -200,6 +211,7 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -220,6 +232,10 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/koketama/errors v1.0.2 h1:v8C8MDiz9QHqzQX587dQpD0bjro8n4GPm4leOELYPyo=
github.com/koketama/errors v1.0.2/go.mod h1:SIU03UGMkhp6tyB5+EvhDI9i9ZDa4rQolJCt9ZrFrbU=
github.com/koketama/urltable v0.1.3 h1:pEuk4/SaqYiTVs70p8GVZeZaLdGCO2Rq8xxKRgMwYwo=
github.com/koketama/urltable v0.1.3/go.mod h1:ime2hsHLQKb3AAJTLulUmYMIEb9YsF2RGSixT9z9ChA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -321,8 +337,11 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A=
github.com/shirou/gopsutil v3.21.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -334,6 +353,8 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
@@ -360,6 +381,10 @@ github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -378,6 +403,7 @@ github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUd
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -393,6 +419,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -423,6 +450,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -447,6 +475,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -462,9 +491,11 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -481,6 +512,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -489,8 +521,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341 h1:2/QtM1mL37YmcsT8HaDNHDgTqqFVw+zr8UzMiBVLzYU=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -528,6 +560,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a h1:pdfjQ7VswBeGam3EpuEJ4e8EAb7JgaubV570LO/SIQM=
golang.org/x/tools v0.0.0-20201226215659-b1c90890d22a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -595,6 +628,8 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek=
gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-5
View File
@@ -1,5 +0,0 @@
## init
项目初始脚本。
- DB 相关 SQL
-10
View File
@@ -1,10 +0,0 @@
CREATE TABLE `user_demo` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',
`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';
+49 -2
View File
@@ -13,14 +13,38 @@ const (
ParamBindError = 10103
AuthorizationError = 10104
CallHTTPError = 10105
ResubmitError = 10106
ResubmitMsg = 10107
HashIdsDecodeError = 10108
SignatureError = 10109
// 模块级错误码 - 用户模块
// 业务模块级错误码
// 用户模块
IllegalUserName = 20101
UserCreateError = 20102
UserUpdateError = 20103
UserSearchError = 20104
// ...
// 授权调用方
AuthorizedCreateError = 20201
AuthorizedListError = 20202
AuthorizedDeleteError = 20203
AuthorizedUpdateError = 20204
AuthorizedDetailError = 20205
AuthorizedCreateAPIError = 20206
AuthorizedListAPIError = 20207
AuthorizedDeleteAPIError = 20208
// 管理员
AdminCreateError = 20301
AdminListError = 20302
AdminDeleteError = 20303
AdminUpdateError = 20304
AdminResetPasswordError = 20305
AdminLoginError = 20307
AdminLogOutError = 20308
AdminModifyPasswordError = 20309
AdminModifyPersonalInfoError = 20310
)
var codeText = map[int]string{
@@ -29,11 +53,34 @@ var codeText = map[int]string{
ParamBindError: "参数信息有误",
AuthorizationError: "签名信息有误",
CallHTTPError: "调用第三方 HTTP 接口失败",
ResubmitError: "Resubmit Error",
ResubmitMsg: "请勿重复提交",
HashIdsDecodeError: "ID 参数有误",
SignatureError: "Signature Error",
IllegalUserName: "非法用户名",
UserCreateError: "创建用户失败",
UserUpdateError: "更新用户失败",
UserSearchError: "查询用户失败",
AuthorizedCreateError: "创建调用方失败",
AuthorizedListError: "获取调用方列表页失败",
AuthorizedDeleteError: "删除调用方失败",
AuthorizedUpdateError: "更新调用方失败",
AuthorizedDetailError: "获取调用方详情失败",
AuthorizedCreateAPIError: "创建调用方API地址失败",
AuthorizedListAPIError: "获取调用方API地址列表失败",
AuthorizedDeleteAPIError: "删除调用方API地址失败",
AdminCreateError: "创建管理员失败",
AdminListError: "获取管理员列表页失败",
AdminDeleteError: "删除管理员失败",
AdminUpdateError: "更新管理员失败",
AdminResetPasswordError: "重置密码失败",
AdminLoginError: "登录失败",
AdminLogOutError: "退出失败",
AdminModifyPasswordError: "修改密码失败",
AdminModifyPersonalInfoError: "修改个人信息失败",
}
func Text(code int) string {
+68
View File
@@ -0,0 +1,68 @@
package admin_handler
import (
"net/http"
"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/pkg/errno"
)
type createRequest struct {
Username string `form:"username"` // 用户名
Nickname string `form:"nickname"` // 昵称
Mobile string `form:"mobile"` // 手机号
Password string `form:"password"` // 密码
}
type createResponse struct {
Id int32 `json:"id"` // 主键ID
}
// Create 新增管理员
// @Summary 新增管理员
// @Description 新增管理员
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param username formData string true "用户名"
// @Param nickname formData string true "昵称"
// @Param mobile formData string true "手机号"
// @Param password formData string true "密码"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
createData := new(admin_service.CreateAdminData)
createData.Nickname = req.Nickname
createData.Username = req.Username
createData.Mobile = req.Mobile
createData.Password = req.Password
id, err := h.adminService.Create(c, createData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminCreateError,
code.Text(code.AdminCreateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
+67
View File
@@ -0,0 +1,67 @@
package admin_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 string `uri:"id"` // HashID
}
type deleteResponse struct {
Id int32 `json:"id"` // 主键ID
}
// Delete 删除管理员
// @Summary 删除管理员
// @Description 删除管理员
// @Tags API.admin
// @Accept json
// @Produce json
// @Param id path string true "hashId"
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/{id} [delete]
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
}
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.adminService.Delete(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminDeleteError,
code.Text(code.AdminDeleteError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,52 @@
package admin_handler
import (
"net/http"
"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/pkg/errno"
"github.com/spf13/cast"
)
type detailResponse struct {
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
// Detail 管理员详情
// @Summary 管理员详情
// @Description 管理员详情
// @Tags API.admin
// @Accept json
// @Produce json
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/info [get]
func (h *handler) Detail() core.HandlerFunc {
return func(c core.Context) {
res := new(detailResponse)
searchOneData := new(admin_service.SearchOneData)
searchOneData.Id = cast.ToInt32(c.UserID())
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
res.Username = info.Username
res.Nickname = info.Nickname
res.Mobile = info.Mobile
c.Payload(res)
}
}
+138
View File
@@ -0,0 +1,138 @@
package admin_handler
import (
"net/http"
"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/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/spf13/cast"
"go.uber.org/zap"
)
type listRequest struct {
Page int `form:"page"` // 第几页
PageSize int `form:"page_size"` // 每页显示条数
Username string `form:"username"` // 用户名
Nickname string `form:"nickname"` // 昵称
Mobile string `form:"mobile"` // 手机号
}
type listData struct {
Id int `json:"id"` // ID
HashID string `json:"hashid"` // hashid
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
IsUsed int `json:"is_used"` // 是否启用 1:是 -1:否
CreatedAt string `json:"created_at"` // 创建时间
CreatedUser string `json:"created_user"` // 创建人
UpdatedAt string `json:"updated_at"` // 更新时间
UpdatedUser string `json:"updated_user"` // 更新人
}
type listResponse struct {
List []listData `json:"list"`
Pagination struct {
Total int `json:"total"`
CurrentPage int `json:"current_page"`
PrePageCount int `json:"pre_page_count"`
} `json:"pagination"`
}
// List 管理员列表
// @Summary 管理员列表
// @Description 管理员列表
// @Tags API.admin
// @Accept json
// @Produce json
// @Param page query int false "第几页"
// @Param page_size query string false "每页显示条数"
// @Param username query string false "用户名"
// @Param nickname query string false "昵称"
// @Param mobile query string false "手机号"
// @Success 200 {object} listResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin [get]
func (h *handler) List() core.HandlerFunc {
return func(c core.Context) {
req := new(listRequest)
res := new(listResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
page := req.Page
if page == 0 {
page = 1
}
pageSize := req.PageSize
if pageSize == 0 {
pageSize = 10
}
searchData := new(admin_service.SearchData)
searchData.Page = page
searchData.PageSize = pageSize
searchData.Username = req.Username
searchData.Nickname = req.Nickname
searchData.Mobile = req.Mobile
resListData, err := h.adminService.PageList(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminListError,
code.Text(code.AdminListError)).WithErr(err),
)
return
}
resCountData, err := h.adminService.PageListCount(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminListError,
code.Text(code.AdminListError)).WithErr(err),
)
return
}
res.Pagination.Total = cast.ToInt(resCountData)
res.Pagination.PrePageCount = pageSize
res.Pagination.CurrentPage = page
res.List = make([]listData, len(resListData))
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
h.logger.Info("hashids err", zap.Error(err))
}
data := listData{
Id: cast.ToInt(v.Id),
HashID: hashId,
Username: v.Username,
Nickname: v.Nickname,
Mobile: v.Mobile,
IsUsed: cast.ToInt(v.IsUsed),
CreatedAt: v.CreatedAt.Format(time_parse.CSTLayout),
CreatedUser: v.CreatedUser,
UpdatedAt: v.UpdatedAt.Format(time_parse.CSTLayout),
UpdatedUser: v.UpdatedUser,
}
res.List[k] = data
}
c.Payload(res)
}
}
@@ -0,0 +1,94 @@
package admin_handler
import (
"encoding/json"
"net/http"
"time"
"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"
)
type loginRequest struct {
Username string `form:"username"` // 用户名
Password string `form:"password"` // 密码
}
type loginResponse struct {
Token string `json:"token"` // 用户身份标识
}
// Login 管理员登录
// @Summary 管理员登录
// @Description 管理员登录
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param username formData string true "用户名"
// @Param password formData string true "密码"
// @Success 200 {object} loginResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/login [post]
func (h *handler) Login() core.HandlerFunc {
return func(c core.Context) {
req := new(loginRequest)
res := new(loginResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
searchOneData := new(admin_service.SearchOneData)
searchOneData.Username = req.Username
searchOneData.Password = password.GeneratePassword(req.Password)
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
)
return
}
if info == nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(errors.New("未查询出符合条件的用户")),
)
return
}
token := password.GenerateLoginToken(info.Id)
// 用户信息
adminJsonInfo, _ := json.Marshal(info)
// 记录 Redis 中
err = h.cache.Set(h.adminService.CacheKeyPrefix()+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
}
res.Token = token
c.Payload(res)
}
}
@@ -0,0 +1,45 @@
package admin_handler
import (
"net/http"
"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"
)
type logoutResponse struct {
Username string `json:"username"` // 用户账号
}
// Logout 管理员登出
// @Summary 管理员登出
// @Description 管理员登出
// @Tags API.admin
// @Accept json
// @Produce json
// @Success 200 {object} logoutResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/login [post]
func (h *handler) Logout() core.HandlerFunc {
return func(c core.Context) {
res := new(logoutResponse)
res.Username = c.UserName()
if !h.cache.Del(h.adminService.CacheKeyPrefix()+password.GenerateLoginToken(cast.ToInt32(c.UserID())), cache.WithTrace(c.Trace())) {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminLogOutError,
code.Text(code.AdminLogOutError)).WithErr(errors.New("cache del err")),
)
return
}
c.Payload(res)
}
}
@@ -0,0 +1,77 @@
package admin_handler
import (
"net/http"
"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/spf13/cast"
)
type modifyPasswordRequest struct {
OldPassword string `form:"old_password"` // 旧密码
NewPassword string `form:"new_password"` // 新密码
}
type modifyPasswordResponse struct {
Username string `json:"username"` // 用户账号
}
// ModifyPassword 修改密码
// @Summary 修改密码
// @Description 修改密码
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param old_password formData string true "旧密码"
// @Param new_password formData string true "新密码"
// @Success 200 {object} modifyPasswordResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/modify_password [patch]
func (h *handler) ModifyPassword() core.HandlerFunc {
return func(c core.Context) {
req := new(modifyPasswordRequest)
res := new(modifyPasswordResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
userId := cast.ToInt32(c.UserID())
searchOneData := new(admin_service.SearchOneData)
searchOneData.Id = userId
searchOneData.Password = password.GeneratePassword(req.OldPassword)
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
if err != nil || info == nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminModifyPasswordError,
code.Text(code.AdminModifyPasswordError)).WithErr(err),
)
return
}
if err := h.adminService.ModifyPassword(c, userId, req.NewPassword); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminModifyPasswordError,
code.Text(code.AdminModifyPasswordError)).WithErr(err),
)
return
}
res.Username = c.UserName()
c.Payload(res)
}
}
@@ -0,0 +1,65 @@
package admin_handler
import (
"net/http"
"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/pkg/errno"
"github.com/spf13/cast"
)
type modifyPersonalInfoRequest struct {
Nickname string `form:"nickname"` // 昵称
Mobile string `form:"mobile"` // 手机号
}
type modifyPersonalInfoResponse struct {
Username string `json:"username"` // 用户账号
}
// ModifyPersonalInfo 修改个人信息
// @Summary 修改个人信息
// @Description 修改个人信息
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param nickname formData string true "昵称"
// @Param mobile formData string true "手机号"
// @Success 200 {object} modifyPersonalInfoResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/modify_password [patch]
func (h *handler) ModifyPersonalInfo() core.HandlerFunc {
return func(c core.Context) {
req := new(modifyPersonalInfoRequest)
res := new(modifyPersonalInfoResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
userId := cast.ToInt32(c.UserID())
modifyData := new(admin_service.ModifyData)
modifyData.Nickname = req.Nickname
modifyData.Mobile = req.Mobile
if err := h.adminService.ModifyPersonalInfo(c, userId, modifyData); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminModifyPersonalInfoError,
code.Text(code.AdminModifyPersonalInfoError)).WithErr(err),
)
return
}
res.Username = c.UserName()
c.Payload(res)
}
}
@@ -0,0 +1,67 @@
package admin_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 resetPasswordRequest struct {
Id string `uri:"id"` // HashID
}
type resetPasswordResponse struct {
Id int32 `json:"id"` // 主键ID
}
// ResetPassword 重置密码
// @Summary 重置密码
// @Description 重置密码
// @Tags API.admin
// @Accept json
// @Produce json
// @Param id path string true "hashId"
// @Success 200 {object} resetPasswordResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/reset_password/{id} [patch]
func (h *handler) ResetPassword() core.HandlerFunc {
return func(c core.Context) {
req := new(resetPasswordRequest)
res := new(resetPasswordResponse)
if err := c.ShouldBindURI(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.adminService.ResetPassword(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminResetPasswordError,
code.Text(code.AdminResetPasswordError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
+69
View File
@@ -0,0 +1,69 @@
package admin_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 updateUsedRequest struct {
Id string `form:"id"` // 主键ID
Used int32 `form:"used"` // 是否启用 1:是 -1:否
}
type updateUsedResponse struct {
Id int32 `json:"id"` // 主键ID
}
// UpdateUsed 更新管理员为启用/禁用
// @Summary 更新管理员为启用/禁用
// @Description 更新管理员为启用/禁用
// @Tags API.admin
// @Accept multipart/form-data
// @Produce json
// @Param id formData string true "Hashid"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/used [patch]
func (h *handler) UpdateUsed() core.HandlerFunc {
return func(c core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
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.adminService.UpdateUsed(c, id, req.Used)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AdminUpdateError,
code.Text(code.AdminUpdateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,86 @@
package admin_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
"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/db"
"github.com/xinliangnote/go-gin-api/pkg/hash"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
i()
// Login 管理员登录
// @Tags API.admin
// @Router /api/admin/login [post]
Login() core.HandlerFunc
// Logout 管理员登出
// @Tags API.admin
// @Router /api/admin/logout [post]
Logout() core.HandlerFunc
// ModifyPassword 修改密码
// @Tags API.admin
// @Router /api/admin/modify_password [patch]
ModifyPassword() core.HandlerFunc
// Detail 个人信息
// @Tags API.admin
// @Router /api/admin/info [get]
Detail() core.HandlerFunc
// ModifyPersonalInfo 修改个人信息
// @Tags API.admin
// @Router /api/admin/modify_personal_info [patch]
ModifyPersonalInfo() core.HandlerFunc
// Create 新增管理员
// @Tags API.admin
// @Router /api/admin [post]
Create() core.HandlerFunc
// List 管理员列表
// @Tags API.admin
// @Router /api/admin [get]
List() core.HandlerFunc
// Delete 删除管理员
// @Tags API.admin
// @Router /api/admin/{id} [delete]
Delete() core.HandlerFunc
// UpdateUsed 更新管理员为启用/禁用
// @Tags API.admin
// @Router /api/admin/used [patch]
UpdateUsed() core.HandlerFunc
// ResetPassword 重置密码
// @Tags API.admin
// @Router /api/admin/reset_password/{id} [patch]
ResetPassword() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
hashids hash.Hash
adminService admin_service.Service
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
hashids: hash.New(configs.Get().HashIds.Secret, configs.Get().HashIds.Length),
adminService: admin_service.New(db, cache),
}
}
func (h *handler) i() {}
+65
View File
@@ -0,0 +1,65 @@
package authorized_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/authorized_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createRequest struct {
BusinessKey string `form:"business_key"` // 调用方key
BusinessDeveloper string `form:"business_developer"` // 调用方对接人
Remark string `form:"remark"` // 备注
}
type createResponse struct {
Id int32 `json:"id"` // 主键ID
}
// Create 新增调用方
// @Summary 新增调用方
// @Description 新增调用方
// @Tags API.authorized
// @Accept multipart/form-data
// @Produce json
// @Param business_key formData string true "调用方key"
// @Param business_developer formData string true "调用方对接人"
// @Param remark formData string true "备注"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
createData := new(authorized_service.CreateAuthorizedData)
createData.BusinessKey = req.BusinessKey
createData.BusinessDeveloper = req.BusinessDeveloper
createData.Remark = req.Remark
id, err := h.authorizedService.Create(c, createData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedCreateError,
code.Text(code.AuthorizedCreateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,88 @@
package authorized_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/authorized_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createAPIRequest struct {
Id string `form:"id"` // HashID
Method string `form:"method"` // 请求方法
API string `form:"api"` // 请求地址
}
type createAPIResponse struct {
Id int32 `json:"id"` // 主键ID
}
// CreateAPI 授权调用方接口地址
// @Summary 授权调用方接口地址
// @Description 授权调用方接口地址
// @Tags API.authorized
// @Accept multipart/form-data
// @Produce json
// @Param id formData string true "HashID"
// @Param method formData string true "请求方法"
// @Param api formData string true "请求地址"
// @Success 200 {object} createAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api [post]
func (h *handler) CreateAPI() core.HandlerFunc {
return func(c core.Context) {
req := new(createAPIRequest)
res := new(createAPIResponse)
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])
// 通过 id 查询出 business_key
authorizedInfo, err := h.authorizedService.Detail(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedDetailError,
code.Text(code.AuthorizedDetailError)).WithErr(err),
)
return
}
createAPIData := new(authorized_service.CreateAuthorizedAPIData)
createAPIData.BusinessKey = authorizedInfo.BusinessKey
createAPIData.Method = req.Method
createAPIData.API = req.API
createId, err := h.authorizedService.CreateAPI(c, createAPIData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedCreateAPIError,
code.Text(code.AuthorizedCreateAPIError)).WithErr(err),
)
return
}
res.Id = createId
c.Payload(res)
}
}
+67
View File
@@ -0,0 +1,67 @@
package authorized_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 string `uri:"id"` // HashID
}
type deleteResponse struct {
Id int32 `json:"id"` // 主键ID
}
// Delete 删除调用方
// @Summary 删除调用方
// @Description 删除调用方
// @Tags API.authorized
// @Accept json
// @Produce json
// @Param id path string true "hashId"
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized/{id} [delete]
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
}
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.authorizedService.Delete(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedDeleteError,
code.Text(code.AuthorizedDeleteError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,67 @@
package authorized_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 deleteAPIRequest struct {
Id string `uri:"id"` // HashID
}
type deleteAPIResponse struct {
Id int32 `json:"id"` // 主键ID
}
// DeleteAPI 删除调用方接口地址
// @Summary 删除调用方接口地址
// @Description 删除调用方接口地址
// @Tags API.authorized
// @Accept json
// @Produce json
// @Param id path string true "主键ID"
// @Success 200 {object} deleteAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api/{id} [delete]
func (h *handler) DeleteAPI() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteAPIRequest)
res := new(deleteAPIResponse)
if err := c.ShouldBindURI(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.authorizedService.DeleteAPI(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedDeleteAPIError,
code.Text(code.AuthorizedDeleteAPIError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
+142
View File
@@ -0,0 +1,142 @@
package authorized_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/authorized_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/spf13/cast"
"go.uber.org/zap"
)
type listRequest struct {
Page int `form:"page"` // 第几页
PageSize int `form:"page_size"` // 每页显示条数
BusinessKey string `form:"business_key"` // 调用方key
BusinessSecret string `form:"business_secret"` // 调用方secret
BusinessDeveloper string `form:"business_developer"` // 调用方对接人
Remark string `form:"remark"` // 备注
}
type listData struct {
Id int `json:"id"` // ID
HashID string `json:"hashid"` // hashid
BusinessKey string `json:"business_key"` // 调用方key
BusinessSecret string `json:"business_secret"` // 调用方secret
BusinessDeveloper string `json:"business_developer"` // 调用方对接人
Remark string `json:"remark"` // 备注
IsUsed int `json:"is_used"` // 是否启用 1:是 -1:否
CreatedAt string `json:"created_at"` // 创建时间
CreatedUser string `json:"created_user"` // 创建人
UpdatedAt string `json:"updated_at"` // 更新时间
UpdatedUser string `json:"updated_user"` // 更新人
}
type listResponse struct {
List []listData `json:"list"`
Pagination struct {
Total int `json:"total"`
CurrentPage int `json:"current_page"`
PrePageCount int `json:"pre_page_count"`
} `json:"pagination"`
}
// List 调用方列表
// @Summary 调用方列表
// @Description 调用方列表
// @Tags API.authorized
// @Accept json
// @Produce json
// @Param page query int false "第几页"
// @Param page_size query string false "每页显示条数"
// @Param business_key query string false "调用方key"
// @Param business_secret query string false "调用方secret"
// @Param business_developer query string false "调用方对接人"
// @Param remark path string false "备注"
// @Success 200 {object} listResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized [get]
func (h *handler) List() core.HandlerFunc {
return func(c core.Context) {
req := new(listRequest)
res := new(listResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
page := req.Page
if page == 0 {
page = 1
}
pageSize := req.PageSize
if pageSize == 0 {
pageSize = 10
}
searchData := new(authorized_service.SearchData)
searchData.Page = page
searchData.PageSize = pageSize
searchData.BusinessKey = req.BusinessKey
searchData.BusinessSecret = req.BusinessSecret
searchData.Remark = req.Remark
resListData, err := h.authorizedService.PageList(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedListError,
code.Text(code.AuthorizedListError)).WithErr(err),
)
return
}
resCountData, err := h.authorizedService.PageListCount(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedListError,
code.Text(code.AuthorizedListError)).WithErr(err),
)
return
}
res.Pagination.Total = cast.ToInt(resCountData)
res.Pagination.PrePageCount = pageSize
res.Pagination.CurrentPage = page
res.List = make([]listData, len(resListData))
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
h.logger.Info("hashids err", zap.Error(err))
}
data := listData{
Id: cast.ToInt(v.Id),
HashID: hashId,
BusinessKey: v.BusinessKey,
BusinessSecret: v.BusinessSecret,
BusinessDeveloper: v.BusinessDeveloper,
Remark: v.Remark,
IsUsed: cast.ToInt(v.IsUsed),
CreatedAt: v.CreatedAt.Format(time_parse.CSTLayout),
CreatedUser: v.CreatedUser,
UpdatedAt: v.UpdatedAt.Format(time_parse.CSTLayout),
UpdatedUser: v.UpdatedUser,
}
res.List[k] = data
}
c.Payload(res)
}
}
+109
View File
@@ -0,0 +1,109 @@
package authorized_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/service/authorized_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
"go.uber.org/zap"
)
type listAPIRequest struct {
Id string `form:"id"` // hashID
}
type listAPIData struct {
HashId string `json:"hash_id"` // hashID
BusinessKey string `json:"business_key"` // 调用方key
Method string `json:"method"` // 调用方secret
API string `json:"api"` // 调用方对接人
}
type listAPIResponse struct {
List []listAPIData `json:"list"`
}
// ListAPI 调用方接口地址列表
// @Summary 调用方接口地址列表
// @Description 调用方接口地址列表
// @Tags API.authorized
// @Accept json
// @Produce json
// @Param business_key query string false "调用方key"
// @Success 200 {object} listAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api [get]
func (h *handler) ListAPI() core.HandlerFunc {
return func(c core.Context) {
req := new(listAPIRequest)
res := new(listAPIResponse)
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])
// 通过 id 查询出 business_key
authorizedInfo, err := h.authorizedService.Detail(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedDetailError,
code.Text(code.AuthorizedDetailError)).WithErr(err),
)
return
}
searchAPIData := new(authorized_service.SearchAPIData)
searchAPIData.BusinessKey = authorizedInfo.BusinessKey
resListData, err := h.authorizedService.ListAPI(c, searchAPIData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedListAPIError,
code.Text(code.AuthorizedListAPIError)).WithErr(err),
)
return
}
res.List = make([]listAPIData, len(resListData))
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
h.logger.Info("hashids err", zap.Error(err))
}
data := listAPIData{
HashId: hashId,
BusinessKey: v.BusinessKey,
Method: v.Method,
API: v.Api,
}
res.List[k] = data
}
c.Payload(res)
}
}
@@ -0,0 +1,69 @@
package authorized_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 updateUsedRequest struct {
Id string `form:"id"` // 主键ID
Used int32 `form:"used"` // 是否启用 1:是 -1:否
}
type updateUsedResponse struct {
Id int32 `json:"id"` // 主键ID
}
// UpdateUsed 更新调用方为启用/禁用
// @Summary 更新调用方为启用/禁用
// @Description 更新调用方为启用/禁用
// @Tags API.authorized
// @Accept multipart/form-data
// @Produce json
// @Param id formData string true "Hashid"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized/used [patch]
func (h *handler) UpdateUsed() core.HandlerFunc {
return func(c core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
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.authorizedService.UpdateUsed(c, id, req.Used)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizedUpdateError,
code.Text(code.AuthorizedUpdateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,71 @@
package authorized_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/service/authorized_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/pkg/hash"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
i()
// Create 新增调用方
// @Tags API.authorized
// @Router /api/authorized [post]
Create() core.HandlerFunc
// CreateAPI 授权调用方接口地址
// @Tags API.authorized
// @Router /api/authorized_api [post]
CreateAPI() core.HandlerFunc
// List 调用方列表
// @Tags API.authorized
// @Router /api/authorized [get]
List() core.HandlerFunc
// ListAPI 调用方接口地址列表
// @Tags API.authorized
// @Router /api/authorized_api [get]
ListAPI() core.HandlerFunc
// Delete 删除调用方
// @Tags API.authorized
// @Router /api/authorized/{id} [delete]
Delete() core.HandlerFunc
// DeleteAPI 删除调用方接口地址
// @Tags API.authorized
// @Router /api/authorized_api/{id} [delete]
DeleteAPI() core.HandlerFunc
// UpdateUsed 更新调用方为启用/禁用
// @Tags API.authorized
// @Router /api/authorized/used [patch]
UpdateUsed() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
authorizedService authorized_service.Service
hashids hash.Hash
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
authorizedService: authorized_service.New(db, cache),
hashids: hash.New(configs.Get().HashIds.Secret, configs.Get().HashIds.Length),
}
}
func (h *handler) i() {}
@@ -28,7 +28,7 @@ type authResponse struct {
func (h *handler) Auth() core.HandlerFunc {
return func(c core.Context) {
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).Sign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
tokenString, err := token.New(cfg.Secret).JwtSign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
@@ -5,7 +5,7 @@ import (
"time"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/repository/third_party_request/go_gin_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/third_party_request/go_gin_api"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
@@ -34,12 +34,12 @@ type traceResponse []struct {
func (h *handler) Trace() core.HandlerFunc {
return func(c core.Context) {
// 三方请求信息
res1, err := go_gin_api_repo.DemoGet("Tom",
res1, err := go_gin_api.DemoGet("Tom",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoGetRetryVerify),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api.DemoGetRetryVerify),
)
if err != nil {
@@ -56,12 +56,12 @@ func (h *handler) Trace() core.HandlerFunc {
p.Println("res1.Name", res1.Name, p.WithTrace(c.Trace()))
// 三方请求信息
res2, err := go_gin_api_repo.DemoPost("Jack",
res2, err := go_gin_api.DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoPostRetryVerify),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api.DemoPostRetryVerify),
)
if err != nil {
@@ -19,9 +19,15 @@ type Handler interface {
Get() core.HandlerFunc
// 示例:支持 post 请求的方法
Post() core.HandlerFunc
// 获取授权信息
// @Tags Demo
// @Router /auth/get [post]
Auth() core.HandlerFunc
// Trace 示例
// @Tags Demo
// @Router /demo/trace [get]
Trace() core.HandlerFunc
}
+56
View File
@@ -0,0 +1,56 @@
package tool_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 hashIdsDecodeRequest struct {
Id string `uri:"id"` // 需解密的密文
}
type hashIdsDecodeResponse struct {
Val int `json:"val"` // 解密后的值
}
// HashIdsDecode HashIds 解密
// @Summary HashIds 解密
// @Description HashIds 解密
// @Tags API.tool
// @Accept json
// @Produce json
// @Param id path string true "需解密的密文"
// @Success 200 {object} hashIdsDecodeResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/hashids/decode/{id} [get]
func (h *handler) HashIdsDecode() core.HandlerFunc {
return func(c core.Context) {
req := new(hashIdsDecodeRequest)
res := new(hashIdsDecodeResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
hashId, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
)
return
}
res.Val = hashId[0]
c.Payload(res)
}
}
+58
View File
@@ -0,0 +1,58 @@
package tool_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/spf13/cast"
)
type hashIdsEncodeRequest struct {
Id int32 `uri:"id"` // 需加密的数字
}
type hashIdsEncodeResponse struct {
Val string `json:"val"` // 加密后的值
}
// HashIdsEncode HashIds 加密
// @Summary HashIds 加密
// @Description HashIds 加密
// @Tags API.tool
// @Accept json
// @Produce json
// @Param id path string true "需加密的数字"
// @Success 200 {object} hashIdsEncodeResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/hashids/encode/{id} [get]
func (h *handler) HashIdsEncode() core.HandlerFunc {
return func(c core.Context) {
req := new(hashIdsEncodeRequest)
res := new(hashIdsEncodeResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(req.Id)})
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
)
return
}
res.Val = hashId
c.Payload(res)
}
}

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