4 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
122 changed files with 12096 additions and 40 deletions
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));
@@ -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,
});
}
+9 -4
View File
@@ -113,22 +113,27 @@
// 选项卡
$('#iframe-content').multitabs({
iframe : true,
refresh : 'no', // iframe中页面是否刷新,'no''从不刷新''nav''点击菜单刷新''all''菜单和tab点击都刷新'
refresh : 'nav', // iframe中页面是否刷新,'no''从不刷新''nav''点击菜单刷新''all''菜单和tab点击都刷新'
nav: {
backgroundColor: '#ffffff',
maxTabs : 35, // 选项卡最大值
},
init : [{
type : 'main',
title : '仪表盘',
url : '/dashboard'
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});
});
});
+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);
});
};
}));
+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>
+32 -3
View File
@@ -3,6 +3,8 @@
<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>
@@ -16,9 +18,10 @@
<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">
@@ -44,6 +47,7 @@
<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(){
@@ -56,9 +60,34 @@
$("#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>
+101 -13
View File
@@ -9,6 +9,7 @@
<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">
@@ -32,21 +33,44 @@
<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 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-code-not-equal-variant"></i> <span>代码生成</span></a>
<ul class="nav nav-subnav">
<li> <a class="multitabs" href="/init">初始化</a> </li>
<li> <a class="multitabs" href="/gormgen">gormgen</a> </li>
<li> <a class="multitabs" href="/handlergen">handlergen</a> </li>
</ul>
</li>
<li class="nav-item"> <a href="/swagger/index.html" target="_blank" ><i class="mdi mdi-file-document-box"></i> <span>接口文档</span></a> </li>
<li class="nav-item"> <a href="/graphql" target="_blank" ><i class="mdi mdi-file-document-box-search"></i> <span>GraphQL</span></a> </li>
<li class="nav-item"> <a href="/metrics" target="_blank" ><i class="mdi mdi-speedometer"></i> <span>接口指标</span></a> </li>
<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>
@@ -183,6 +207,31 @@
</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>
@@ -206,6 +255,45 @@
<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>
+63 -2
View File
@@ -2,7 +2,9 @@ package main
import (
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/xinliangnote/go-gin-api/cmd/init/db/mysql"
@@ -42,11 +44,70 @@ func main() {
}
}()
// 开启事务
tx := db.GetDb().Begin()
// 创建 user_demo 表
err = db.GetDb().Exec(mysql.CreateUserDemoTableSql()).Error
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()
log.Println("create user_demo table success")
}
+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
}
+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())
}
+9
View File
@@ -58,6 +58,11 @@ type Config struct {
Secret string `toml:"secret"`
ExpireDuration time.Duration `toml:"expireDuration"`
} `toml:"urlToken"`
HashIds struct {
Secret string `toml:"secret"`
Length int `toml:"length"`
} `toml:"hashids"`
}
func init() {
@@ -89,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"
}
+5 -1
View File
@@ -36,4 +36,8 @@ expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时)
[urlToken]
secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)
[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:
+3 -1
View File
@@ -16,13 +16,15 @@ require (
github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.5.4 // indirect
github.com/jinzhu/gorm v1.9.16
github.com/koketama/errors v1.0.0
github.com/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
+6 -2
View File
@@ -232,8 +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.0 h1:4JO7SGSkBROkTuacd+cgPtBDr7fisixXoajkNh4eW3M=
github.com/koketama/errors v1.0.0/go.mod h1:SIU03UGMkhp6tyB5+EvhDI9i9ZDa4rQolJCt9ZrFrbU=
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=
@@ -351,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=
+45 -2
View File
@@ -15,14 +15,36 @@ const (
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{
@@ -33,11 +55,32 @@ var codeText = map[int]string{
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() {}
+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)
}
}
@@ -0,0 +1,43 @@
package tool_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
"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()
// HashIdsEncode HashIds 加密
// @Tags API.tool
// @Router /api/tool/hashids/encode/{id} [get]
HashIdsEncode() core.HandlerFunc
// HashIdsDecode HashIds 解密
// @Tags API.tool
// @Router /api/tool/hashids/decode/{id} [get]
HashIdsDecode() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
hashids hash.Hash
}
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),
}
}
func (h *handler) i() {}
+583
View File
@@ -0,0 +1,583 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package admin_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *Admin {
return new(Admin)
}
func NewQueryBuilder() *adminRepoQueryBuilder {
return new(adminRepoQueryBuilder)
}
func (t *Admin) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *Admin) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *Admin) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&Admin{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type adminRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *adminRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *adminRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&Admin{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *adminRepoQueryBuilder) First(db *gorm.DB) (*Admin, error) {
ret := &Admin{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *adminRepoQueryBuilder) QueryOne(db *gorm.DB) (*Admin, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *adminRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*Admin, error) {
var ret []*Admin
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *adminRepoQueryBuilder) Limit(limit int) *adminRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *adminRepoQueryBuilder) Offset(offset int) *adminRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *adminRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIdIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIdNotIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderById(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereUsername(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "username", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUsernameIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "username", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUsernameNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "username", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByUsername(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "username "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WherePassword(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "password", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WherePasswordIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "password", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WherePasswordNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "password", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByPassword(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "password "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereNickname(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nickname", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereNicknameIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nickname", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereNicknameNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nickname", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByNickname(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "nickname "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereMobileIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereMobileNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByMobile(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "mobile "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsUsed(p db_repo.Predicate, value int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsUsedIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsUsedNotIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByIsUsed(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_used "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsDeletedIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereIsDeletedNotIn(value []int32) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByIsDeleted(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedAtIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedAtNotIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByCreatedAt(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedUser(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedUserIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereCreatedUserNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByCreatedUser(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_user "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedAtIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedAtNotIn(value []time.Time) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByUpdatedAt(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedUser(p db_repo.Predicate, value string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", p),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedUserIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) WhereUpdatedUserNotIn(value []string) *adminRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "NOT IN"),
value,
})
return qb
}
func (qb *adminRepoQueryBuilder) OrderByUpdatedUser(asc bool) *adminRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_user "+order)
return qb
}
+19
View File
@@ -0,0 +1,19 @@
package admin_repo
import "time"
// 管理员表
//go:generate gormgen -structs Admin -input .
type Admin struct {
Id int32 // 主键
Username string // 用户名
Password string // 密码
Nickname string // 昵称
Mobile string // 手机号
IsUsed int32 // 是否启用 1:是 -1:否
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
CreatedUser string // 创建人
UpdatedAt time.Time `gorm:"time"` // 更新时间
UpdatedUser string // 更新人
}
+16
View File
@@ -0,0 +1,16 @@
#### go_gin_api.admin
管理员表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | username | 用户名 | varchar(32) | UNI | NO | | |
| 3 | password | 密码 | varchar(32) | | NO | | |
| 4 | nickname | 昵称 | varchar(60) | | NO | | |
| 5 | mobile | 手机号 | varchar(20) | | NO | | |
| 6 | is_used | 是否启用 1:是 -1:否 | tinyint(1) | | NO | | 1 |
| 7 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 8 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 9 | created_user | 创建人 | varchar(60) | | NO | | |
| 10 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
| 11 | updated_user | 更新人 | varchar(60) | | NO | | |
@@ -0,0 +1,497 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package authorized_api_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *AuthorizedApi {
return new(AuthorizedApi)
}
func NewQueryBuilder() *authorizedApiRepoQueryBuilder {
return new(authorizedApiRepoQueryBuilder)
}
func (t *AuthorizedApi) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *AuthorizedApi) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *AuthorizedApi) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&AuthorizedApi{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type authorizedApiRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *authorizedApiRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *authorizedApiRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&AuthorizedApi{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *authorizedApiRepoQueryBuilder) First(db *gorm.DB) (*AuthorizedApi, error) {
ret := &AuthorizedApi{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *authorizedApiRepoQueryBuilder) QueryOne(db *gorm.DB) (*AuthorizedApi, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *authorizedApiRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*AuthorizedApi, error) {
var ret []*AuthorizedApi
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *authorizedApiRepoQueryBuilder) Limit(limit int) *authorizedApiRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *authorizedApiRepoQueryBuilder) Offset(offset int) *authorizedApiRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereIdIn(value []int32) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereIdNotIn(value []int32) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderById(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereBusinessKey(p db_repo.Predicate, value string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_key", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereBusinessKeyIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_key", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereBusinessKeyNotIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_key", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByBusinessKey(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "business_key "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereMethod(p db_repo.Predicate, value string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "method", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereMethodIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "method", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereMethodNotIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "method", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByMethod(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "method "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereApi(p db_repo.Predicate, value string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "api", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereApiIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "api", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereApiNotIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "api", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByApi(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "api "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereIsDeletedIn(value []int32) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereIsDeletedNotIn(value []int32) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByIsDeleted(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereCreatedAtIn(value []time.Time) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereCreatedAtNotIn(value []time.Time) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByCreatedAt(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereCreatedUser(p db_repo.Predicate, value string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereCreatedUserIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereCreatedUserNotIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByCreatedUser(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_user "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereUpdatedAtIn(value []time.Time) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereUpdatedAtNotIn(value []time.Time) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByUpdatedAt(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereUpdatedUser(p db_repo.Predicate, value string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", p),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereUpdatedUserIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) WhereUpdatedUserNotIn(value []string) *authorizedApiRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedApiRepoQueryBuilder) OrderByUpdatedUser(asc bool) *authorizedApiRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_user "+order)
return qb
}
@@ -0,0 +1,17 @@
package authorized_api_repo
import "time"
// 已授权的调用方表
//go:generate gormgen -structs AuthorizedApi -input .
type AuthorizedApi struct {
Id int32 // 主键
BusinessKey string // 调用方key
Method string // 请求方式
Api string // 请求地址
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
CreatedUser string // 创建人
UpdatedAt time.Time `gorm:"time"` // 更新时间
UpdatedUser string // 更新人
}
@@ -0,0 +1,14 @@
#### go_gin_api.authorized_api
已授权的调用方表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | business_key | 调用方key | varchar(30) | | NO | | |
| 3 | method | 请求方式 | varchar(30) | | NO | | |
| 4 | api | 请求地址 | varchar(100) | | NO | | |
| 5 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 6 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 7 | created_user | 创建人 | varchar(60) | | NO | | |
| 8 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
| 9 | updated_user | 更新人 | varchar(60) | | NO | | |
@@ -0,0 +1,583 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package authorized_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *Authorized {
return new(Authorized)
}
func NewQueryBuilder() *authorizedRepoQueryBuilder {
return new(authorizedRepoQueryBuilder)
}
func (t *Authorized) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *Authorized) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *Authorized) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&Authorized{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type authorizedRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *authorizedRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *authorizedRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&Authorized{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *authorizedRepoQueryBuilder) First(db *gorm.DB) (*Authorized, error) {
ret := &Authorized{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *authorizedRepoQueryBuilder) QueryOne(db *gorm.DB) (*Authorized, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *authorizedRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*Authorized, error) {
var ret []*Authorized
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *authorizedRepoQueryBuilder) Limit(limit int) *authorizedRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *authorizedRepoQueryBuilder) Offset(offset int) *authorizedRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIdIn(value []int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIdNotIn(value []int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderById(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessKey(p db_repo.Predicate, value string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_key", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessKeyIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_key", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessKeyNotIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_key", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByBusinessKey(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "business_key "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessSecret(p db_repo.Predicate, value string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_secret", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessSecretIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_secret", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessSecretNotIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_secret", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByBusinessSecret(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "business_secret "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessDeveloper(p db_repo.Predicate, value string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_developer", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessDeveloperIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_developer", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereBusinessDeveloperNotIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "business_developer", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByBusinessDeveloper(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "business_developer "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereRemark(p db_repo.Predicate, value string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "remark", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereRemarkIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "remark", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereRemarkNotIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "remark", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByRemark(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "remark "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIsUsed(p db_repo.Predicate, value int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIsUsedIn(value []int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIsUsedNotIn(value []int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_used", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByIsUsed(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_used "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIsDeletedIn(value []int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereIsDeletedNotIn(value []int32) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByIsDeleted(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereCreatedAtIn(value []time.Time) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereCreatedAtNotIn(value []time.Time) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByCreatedAt(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereCreatedUser(p db_repo.Predicate, value string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereCreatedUserIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereCreatedUserNotIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_user", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByCreatedUser(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_user "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereUpdatedAtIn(value []time.Time) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereUpdatedAtNotIn(value []time.Time) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByUpdatedAt(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereUpdatedUser(p db_repo.Predicate, value string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", p),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereUpdatedUserIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) WhereUpdatedUserNotIn(value []string) *authorizedRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_user", "NOT IN"),
value,
})
return qb
}
func (qb *authorizedRepoQueryBuilder) OrderByUpdatedUser(asc bool) *authorizedRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_user "+order)
return qb
}
@@ -0,0 +1,19 @@
package authorized_repo
import "time"
// 已授权的调用方表
//go:generate gormgen -structs Authorized -input .
type Authorized struct {
Id int32 // 主键
BusinessKey string // 调用方key
BusinessSecret string // 调用方secret
BusinessDeveloper string // 调用方对接人
Remark string // 备注
IsUsed int32 // 是否启用 1:是 -1:否
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
CreatedUser string // 创建人
UpdatedAt time.Time `gorm:"time"` // 更新时间
UpdatedUser string // 更新人
}
@@ -0,0 +1,16 @@
#### go_gin_api.authorized
已授权的调用方表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | business_key | 调用方key | varchar(32) | | NO | | |
| 3 | business_secret | 调用方secret | varchar(60) | | NO | | |
| 4 | business_developer | 调用方对接人 | varchar(60) | | NO | | |
| 5 | remark | 备注 | varchar(255) | | NO | | |
| 6 | is_used | 是否启用 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 7 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 8 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 9 | created_user | 创建人 | varchar(60) | | NO | | |
| 10 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
| 11 | updated_user | 更新人 | varchar(60) | | NO | | |
@@ -0,0 +1,48 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
)
var _ Service = (*service)(nil)
// 定义缓存前缀
var cacheKeyPrefix = configs.ProjectName() + ":admin:"
type Service interface {
i()
CacheKeyPrefix() (pre string)
Create(ctx core.Context, authorizedData *CreateAdminData) (id int32, err error)
PageList(ctx core.Context, searchData *SearchData) (listData []*admin_repo.Admin, err error)
PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error)
UpdateUsed(ctx core.Context, id int32, used int32) (err error)
Delete(ctx core.Context, id int32) (err error)
Detail(ctx core.Context, searchOneData *SearchOneData) (info *admin_repo.Admin, err error)
ResetPassword(ctx core.Context, id int32) (err error)
ModifyPassword(ctx core.Context, id int32, newPassword string) (err error)
ModifyPersonalInfo(ctx core.Context, id int32, modifyData *ModifyData) (err error)
}
type service struct {
db db.Repo
cache cache.Repo
}
func New(db db.Repo, cache cache.Repo) Service {
return &service{
db: db,
cache: cache,
}
}
func (s *service) i() {}
func (s *service) CacheKeyPrefix() (pre string) {
pre = cacheKeyPrefix
return
}
@@ -0,0 +1,31 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
type CreateAdminData struct {
Username string // 用户名
Nickname string // 昵称
Mobile string // 手机号
Password string // 密码
}
func (s *service) Create(ctx core.Context, adminData *CreateAdminData) (id int32, err error) {
model := admin_repo.NewModel()
model.Username = adminData.Username
model.Password = password.GeneratePassword(adminData.Password)
model.Nickname = adminData.Nickname
model.Mobile = adminData.Mobile
model.CreatedUser = ctx.UserName()
model.IsUsed = 1
model.IsDeleted = -1
id, err = model.Create(s.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) Delete(ctx core.Context, id int32) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,53 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchOneData struct {
Id int32 // 用户ID
Username string // 用户名
Nickname string // 昵称
Mobile string // 手机号
Password string // 密码
IsUsed int32 // 是否启用 1:是 -1:否
}
func (s *service) Detail(ctx core.Context, searchOneData *SearchOneData) (info *admin_repo.Admin, err error) {
qb := admin_repo.NewQueryBuilder()
qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchOneData.Id != 0 {
qb.WhereId(db_repo.EqualPredicate, searchOneData.Id)
}
if searchOneData.Username != "" {
qb.WhereUsername(db_repo.EqualPredicate, searchOneData.Username)
}
if searchOneData.Nickname != "" {
qb.WhereNickname(db_repo.EqualPredicate, searchOneData.Nickname)
}
if searchOneData.Mobile != "" {
qb.WhereMobile(db_repo.EqualPredicate, searchOneData.Mobile)
}
if searchOneData.Password != "" {
qb.WherePassword(db_repo.EqualPredicate, searchOneData.Password)
}
if searchOneData.IsUsed != 0 {
qb.WhereIsUsed(db_repo.EqualPredicate, searchOneData.IsUsed)
}
info, err = qb.QueryOne(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) ModifyPassword(ctx core.Context, id int32, newPassword string) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"password": password.GeneratePassword(newPassword),
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,29 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type ModifyData struct {
Nickname string // 昵称
Mobile string // 手机号
}
func (s *service) ModifyPersonalInfo(ctx core.Context, id int32, modifyData *ModifyData) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"nickname": modifyData.Nickname,
"mobile": modifyData.Mobile,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
return
}
@@ -0,0 +1,56 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchData struct {
Page int // 第几页
PageSize int // 每页显示条数
Username string // 用户名
Nickname string // 昵称
Mobile string // 手机号
}
func (s *service) PageList(ctx core.Context, searchData *SearchData) (listData []*admin_repo.Admin, err error) {
page := searchData.Page
if page == 0 {
page = 1
}
pageSize := searchData.PageSize
if pageSize == 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
qb := admin_repo.NewQueryBuilder()
qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchData.Username != "" {
qb.WhereUsername(db_repo.EqualPredicate, searchData.Username)
}
if searchData.Nickname != "" {
qb.WhereNickname(db_repo.EqualPredicate, searchData.Nickname)
}
if searchData.Mobile != "" {
qb.WhereMobile(db_repo.EqualPredicate, searchData.Mobile)
}
listData, err = qb.
Limit(pageSize).
Offset(offset).
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,31 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (s *service) PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error) {
qb := admin_repo.NewQueryBuilder()
qb = qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchData.Username != "" {
qb.WhereUsername(db_repo.EqualPredicate, searchData.Username)
}
if searchData.Nickname != "" {
qb.WhereNickname(db_repo.EqualPredicate, searchData.Nickname)
}
if searchData.Mobile != "" {
qb.WhereMobile(db_repo.EqualPredicate, searchData.Mobile)
}
total, err = qb.Count(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) ResetPassword(ctx core.Context, id int32) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"password": password.ResetPassword(),
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,26 @@
package admin_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/admin_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
)
func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error) {
model := admin_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_used": used,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+password.GenerateLoginToken(id), cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,46 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
)
var _ Service = (*service)(nil)
// 定义缓存前缀
var cacheKeyPrefix = configs.ProjectName() + ":authorized:"
type Service interface {
i()
Create(ctx core.Context, authorizedData *CreateAuthorizedData) (id int32, err error)
List(ctx core.Context, searchData *SearchData) (listData []*authorized_repo.Authorized, err error)
PageList(ctx core.Context, searchData *SearchData) (listData []*authorized_repo.Authorized, err error)
PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error)
UpdateUsed(ctx core.Context, id int32, used int32) (err error)
Delete(ctx core.Context, id int32) (err error)
Detail(ctx core.Context, id int32) (info *authorized_repo.Authorized, err error)
DetailByKey(ctx core.Context, key string) (data *CacheAuthorizedData, err error)
CreateAPI(ctx core.Context, authorizedAPIData *CreateAuthorizedAPIData) (id int32, err error)
ListAPI(ctx core.Context, searchAPIData *SearchAPIData) (listData []*authorized_api_repo.AuthorizedApi, err error)
DeleteAPI(ctx core.Context, id int32) (err error)
}
type service struct {
db db.Repo
cache cache.Repo
}
func New(db db.Repo, cache cache.Repo) Service {
return &service{
db: db,
cache: cache,
}
}
func (s *service) i() {}
@@ -0,0 +1,37 @@
package authorized_service
import (
"crypto/rand"
"encoding/hex"
"io"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type CreateAuthorizedData struct {
BusinessKey string `json:"business_key"` // 调用方key
BusinessDeveloper string `json:"business_developer"` // 调用方对接人
Remark string `json:"remark"` // 备注
}
func (s *service) Create(ctx core.Context, authorizedData *CreateAuthorizedData) (id int32, err error) {
buf := make([]byte, 10)
io.ReadFull(rand.Reader, buf)
secret := string(hex.EncodeToString(buf))
model := authorized_repo.NewModel()
model.BusinessKey = authorizedData.BusinessKey
model.BusinessSecret = secret
model.BusinessDeveloper = authorizedData.BusinessDeveloper
model.Remark = authorizedData.Remark
model.CreatedUser = ctx.UserName()
model.IsUsed = 1
model.IsDeleted = -1
id, err = model.Create(s.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,30 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type CreateAuthorizedAPIData struct {
BusinessKey string `json:"business_key"` // 调用方key
Method string `json:"method"` // 请求方法
API string `json:"api"` // 请求地址
}
func (s *service) CreateAPI(ctx core.Context, authorizedAPIData *CreateAuthorizedAPIData) (id int32, err error) {
model := authorized_api_repo.NewModel()
model.BusinessKey = authorizedAPIData.BusinessKey
model.Method = authorizedAPIData.Method
model.Api = authorizedAPIData.API
model.CreatedUser = ctx.UserName()
model.IsDeleted = -1
id, err = model.Create(s.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
s.cache.Del(cacheKeyPrefix+authorizedAPIData.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,38 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"gorm.io/gorm"
)
func (s *service) Delete(ctx core.Context, id int32) (err error) {
// 先查询 id 是否存在
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereId(db_repo.EqualPredicate, id).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err == gorm.ErrRecordNotFound {
return nil
}
model := authorized_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,38 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"gorm.io/gorm"
)
func (s *service) DeleteAPI(ctx core.Context, id int32) (err error) {
// 先查询 id 是否存在
authorizedApiInfo, err := authorized_api_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereId(db_repo.EqualPredicate, id).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err == gorm.ErrRecordNotFound {
return nil
}
model := authorized_api_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+authorizedApiInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
@@ -0,0 +1,20 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (s *service) Detail(ctx core.Context, id int32) (info *authorized_repo.Authorized, err error) {
qb := authorized_repo.NewQueryBuilder()
qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
qb.WhereId(db_repo.EqualPredicate, id)
info, err = qb.First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,80 @@
package authorized_service
import (
"encoding/json"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
// 定义缓存结构
type CacheAuthorizedData struct {
Key string `json:"key"` // 调用方 key
Secret string `json:"secret"` // 调用方 secret
IsUsed int32 `json:"is_used"` // 调用方启用状态 1=启用 -1=禁用
Apis []cacheApiData `json:"apis"` // 调用方授权的 Apis
}
type cacheApiData struct {
Method string `json:"method"` // 请求方式
Api string `json:"api"` // 请求地址
}
func (s *service) DetailByKey(ctx core.Context, key string) (cacheData *CacheAuthorizedData, err error) {
// 查询缓存
cacheKey := cacheKeyPrefix + key
value, err := s.cache.Get(cacheKey, cache.WithTrace(ctx.RequestContext().Trace))
cacheData = new(CacheAuthorizedData)
if err == nil && json.Unmarshal([]byte(value), cacheData) == nil {
return
}
// 查询调用方信息
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
// 查询调用方授权 API 信息
authorizedApiInfo, err := authorized_api_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereBusinessKey(db_repo.EqualPredicate, key).
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
// 设置缓存 data
cacheData = new(CacheAuthorizedData)
cacheData.Key = key
cacheData.Secret = authorizedInfo.BusinessSecret
cacheData.IsUsed = authorizedInfo.IsUsed
cacheData.Apis = make([]cacheApiData, len(authorizedApiInfo))
for k, v := range authorizedApiInfo {
data := cacheApiData{
Method: v.Method,
Api: v.Api,
}
cacheData.Apis[k] = data
}
cacheDataByte, _ := json.Marshal(cacheData)
err = s.cache.Set(cacheKey, string(cacheDataByte), 0, cache.WithTrace(ctx.Trace()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,34 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (s *service) List(ctx core.Context, searchData *SearchData) (listData []*authorized_repo.Authorized, err error) {
qb := authorized_repo.NewQueryBuilder()
qb = qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchData.BusinessKey != "" {
qb.WhereBusinessKey(db_repo.EqualPredicate, searchData.BusinessKey)
}
if searchData.BusinessSecret != "" {
qb.WhereBusinessSecret(db_repo.EqualPredicate, searchData.BusinessSecret)
}
if searchData.BusinessDeveloper != "" {
qb.WhereBusinessDeveloper(db_repo.EqualPredicate, searchData.BusinessDeveloper)
}
listData, err = qb.
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,30 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_api_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchAPIData struct {
BusinessKey string `json:"business_key"` // 调用方key
}
func (s *service) ListAPI(ctx core.Context, searchAPIData *SearchAPIData) (listData []*authorized_api_repo.AuthorizedApi, err error) {
qb := authorized_api_repo.NewQueryBuilder()
qb = qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchAPIData.BusinessKey != "" {
qb.WhereBusinessKey(db_repo.EqualPredicate, searchAPIData.BusinessKey)
}
listData, err = qb.
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,57 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type SearchData struct {
Page int `json:"page"` // 第几页
PageSize int `json:"page_size"` // 每页显示条数
BusinessKey string `json:"business_key"` // 调用方key
BusinessSecret string `json:"business_secret"` // 调用方secret
BusinessDeveloper string `json:"business_developer"` // 调用方对接人
Remark string `json:"remark"` // 备注
}
func (s *service) PageList(ctx core.Context, searchData *SearchData) (listData []*authorized_repo.Authorized, err error) {
page := searchData.Page
if page == 0 {
page = 1
}
pageSize := searchData.PageSize
if pageSize == 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
qb := authorized_repo.NewQueryBuilder()
qb = qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchData.BusinessKey != "" {
qb.WhereBusinessKey(db_repo.EqualPredicate, searchData.BusinessKey)
}
if searchData.BusinessSecret != "" {
qb.WhereBusinessSecret(db_repo.EqualPredicate, searchData.BusinessSecret)
}
if searchData.BusinessDeveloper != "" {
qb.WhereBusinessDeveloper(db_repo.EqualPredicate, searchData.BusinessDeveloper)
}
listData, err = qb.
Limit(pageSize).
Offset(offset).
OrderById(false).
QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return nil, err
}
return
}
@@ -0,0 +1,31 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (s *service) PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error) {
qb := authorized_repo.NewQueryBuilder()
qb = qb.WhereIsDeleted(db_repo.EqualPredicate, -1)
if searchData.BusinessKey != "" {
qb.WhereBusinessKey(db_repo.EqualPredicate, searchData.BusinessKey)
}
if searchData.BusinessSecret != "" {
qb.WhereBusinessSecret(db_repo.EqualPredicate, searchData.BusinessSecret)
}
if searchData.BusinessDeveloper != "" {
qb.WhereBusinessDeveloper(db_repo.EqualPredicate, searchData.BusinessDeveloper)
}
total, err = qb.Count(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,37 @@
package authorized_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/authorized_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"gorm.io/gorm"
)
func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error) {
authorizedInfo, err := authorized_repo.NewQueryBuilder().
WhereIsDeleted(db_repo.EqualPredicate, -1).
WhereId(db_repo.EqualPredicate, id).
First(s.db.GetDbR().WithContext(ctx.RequestContext()))
if err == gorm.ErrRecordNotFound {
return nil
}
model := authorized_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"is_used": used,
"updated_user": ctx.UserName(),
}
err = model.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
s.cache.Del(cacheKeyPrefix+authorizedInfo.BusinessKey, cache.WithTrace(ctx.Trace()))
return
}
+20 -5
View File
@@ -33,7 +33,7 @@ type Repo interface {
TTL(key string) (time.Duration, error)
Expire(key string, ttl time.Duration) bool
ExpireAt(key string, ttl time.Time) bool
Del(keys ...string) bool
Del(key string, options ...Option) bool
Exists(keys ...string) bool
Incr(key string, options ...Option) int64
Close() error
@@ -158,13 +158,28 @@ func (c *cacheRepo) Exists(keys ...string) bool {
return value > 0
}
// Del del some key from redis
func (c *cacheRepo) Del(keys ...string) bool {
if len(keys) == 0 {
func (c *cacheRepo) Del(key string, options ...Option) bool {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "del"
opt.Redis.Key = key
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
if key == "" {
return true
}
value, _ := c.client.Del(keys...).Result()
value, _ := c.client.Del(key).Result()
return value > 0
}
+14
View File
@@ -10,6 +10,7 @@ import (
"github.com/xinliangnote/go-gin-api/configs"
_ "github.com/xinliangnote/go-gin-api/docs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/pkg/browser"
"github.com/xinliangnote/go-gin-api/pkg/color"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/errno"
@@ -48,6 +49,7 @@ type option struct {
recordMetrics RecordMetrics
enableCors bool
enableRate bool
enableOpenBrowser string
}
// OnPanicNotify 发生panic时通知用
@@ -93,6 +95,13 @@ func WithRecordMetrics(record RecordMetrics) Option {
}
}
// WithEnableOpenBrowser 启动后在浏览器中打开 uri
func WithEnableOpenBrowser(uri string) Option {
return func(opt *option) {
opt.enableOpenBrowser = uri
}
}
// WithEnableCors 开启CORS
func WithEnableCors() Option {
return func(opt *option) {
@@ -311,6 +320,11 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}))
}
if opt.enableOpenBrowser != "" {
_ = browser.Open(opt.enableOpenBrowser)
fmt.Println(color.Green("* [register open browser '" + opt.enableOpenBrowser + "']"))
}
// recover两次,防止处理时发生panic,尤其是在OnPanicNotify中。
mux.engine.Use(func(ctx *gin.Context) {
defer func() {
+45
View File
@@ -0,0 +1,45 @@
package password
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"fmt"
)
const salt = "qkhPAGA13HocW3GAEWwb"
const defaultPassword = "123456"
func GeneratePassword(str string) (password string) {
// md5
m := md5.New()
m.Write([]byte(str))
mByte := m.Sum(nil)
// hmac
h := hmac.New(sha256.New, []byte(salt))
h.Write(mByte)
password = hex.EncodeToString(h.Sum(nil))
return
}
func ResetPassword() (password string) {
m := md5.New()
m.Write([]byte(defaultPassword))
mStr := hex.EncodeToString(m.Sum(nil))
password = GeneratePassword(mStr)
return
}
func GenerateLoginToken(id int32) (token string) {
m := md5.New()
m.Write([]byte(fmt.Sprintf("%d%s", id, salt)))
token = hex.EncodeToString(m.Sum(nil))
return
}
@@ -0,0 +1,125 @@
package middleware
import (
"net/http"
"strings"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/signature"
"github.com/koketama/urltable"
"github.com/pkg/errors"
)
const ttl = time.Minute * 2 // 签名超时时间 2 分钟
var whiteListPath = map[string]bool{
"/login/web": true,
}
func (m *middleware) Signature() core.HandlerFunc {
return func(c core.Context) {
// 签名信息
authorization := c.GetHeader("Authorization")
if authorization == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中缺少 Authorization 参数")),
)
return
}
// 时间信息
date := c.GetHeader("Authorization-Date")
if date == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中缺少 Date 参数")),
)
return
}
// 通过签名信息获取 key
authorizationSplit := strings.Split(authorization, " ")
if len(authorizationSplit) < 2 {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中 Authorization 格式错误")),
)
return
}
key := authorizationSplit[0]
data, err := m.authorizedService.DetailByKey(c, key)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(err),
)
return
}
// 验证 cache 是否被调用
if data.IsUsed == -1 {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New(key + " 已被禁止调用")),
)
return
}
if len(data.Apis) < 1 {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New(key + " 未进行接口授权")),
)
return
}
if !whiteListPath[c.Path()] {
// 验证 c.Method() + c.Path() 是否授权
table := urltable.NewTable()
for _, v := range data.Apis {
_ = table.Append(v.Method + v.Api)
}
if pattern, _ := table.Mapping(c.Method() + c.Path()); pattern == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New(c.Method() + c.Path() + " 未进行接口授权")),
)
return
}
}
ok, err := signature.New(key, data.Secret, ttl).Verify(authorization, date, c.Path(), c.Method(), c.RequestInputParams())
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(err),
)
return
}
if !ok {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.SignatureError,
code.Text(code.SignatureError)).WithErr(errors.New("Header 中 Authorization 信息错误")),
)
return
}
}
}
@@ -0,0 +1,57 @@
package middleware
import (
"encoding/json"
"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/pkg/errno"
"github.com/pkg/errors"
)
func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err errno.Error) {
token := ctx.GetHeader("Token")
if token == "" {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Token 参数"))
return
}
if !m.cache.Exists(m.adminService.CacheKeyPrefix() + token) {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("请先登录"))
return
}
cacheData, cacheErr := m.cache.Get(m.adminService.CacheKeyPrefix()+token, cache.WithTrace(ctx.Trace()))
if cacheErr != nil {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(cacheErr)
return
}
type userInfo struct {
Id int64 `json:"id"` // 用户ID
Username string `json:"username"` // 用户名
}
var userData userInfo
_ = json.Unmarshal([]byte(cacheData), &userData)
userId = userData.Id
userName = userData.Username
return
}
+20 -5
View File
@@ -1,8 +1,11 @@
package middleware
import (
"github.com/xinliangnote/go-gin-api/internal/api/service/admin_service"
"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/errno"
"go.uber.org/zap"
@@ -22,17 +25,29 @@ type Middleware interface {
// DisableLog 不记录日志
DisableLog() core.HandlerFunc
// Signature 签名验证,对用签名算法 pkg/signature
Signature() core.HandlerFunc
// Token 签名验证,对登录用户的验证
Token(ctx core.Context) (userId int64, userName string, err errno.Error)
}
type middleware struct {
logger *zap.Logger
cache cache.Repo
logger *zap.Logger
cache cache.Repo
db db.Repo
authorizedService authorized_service.Service
adminService admin_service.Service
}
func New(logger *zap.Logger, cache cache.Repo) Middleware {
func New(logger *zap.Logger, cache cache.Repo, db db.Repo) Middleware {
return &middleware{
logger: logger,
cache: cache,
logger: logger,
cache: cache,
db: db,
authorizedService: authorized_service.New(db, cache),
adminService: admin_service.New(db, cache),
}
}
+10 -1
View File
@@ -1,6 +1,7 @@
package router
import (
"github.com/xinliangnote/go-gin-api/configs"
"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"
@@ -8,6 +9,7 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
"github.com/xinliangnote/go-gin-api/internal/router/middleware"
"github.com/xinliangnote/go-gin-api/pkg/file"
"github.com/pkg/errors"
"go.uber.org/zap"
@@ -23,12 +25,19 @@ type resource struct {
}
func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) (core.Mux, error) {
var openBrowserUri = "http://127.0.0.1:9999"
_, ok := file.IsExists(configs.InitDBLockFile())
if !ok {
openBrowserUri = "http://127.0.0.1:9999/init?init=db"
}
if logger == nil {
return nil, errors.New("logger required")
}
mux, err := core.New(logger,
core.WithEnableOpenBrowser(openBrowserUri),
core.WithEnableCors(),
core.WithEnableRate(),
core.WithPanicNotify(notify.OnPanicNotify),
@@ -45,7 +54,7 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C
r.db = db
r.cache = cache
r.grpConn = grpConn
r.middles = middleware.New(logger, cache)
r.middles = middleware.New(logger, cache, db)
// 设置 WEB 路由
setWebRouter(r)
+43
View File
@@ -1,7 +1,10 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/admin_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/authorized_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/tool_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
@@ -33,4 +36,44 @@ func setApiRouter(r *resource) {
user.PATCH("/delete/:id", userHandler.Delete())
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
}
// authorized
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
// admin
adminHandler := admin_handler.New(r.logger, r.db, r.cache)
// 登录
login := r.mux.Group("/login", r.middles.Signature())
{
login.POST("/web", adminHandler.Login())
}
// api
api := r.mux.Group("/api", core.WrapAuthHandler(r.middles.Token), r.middles.Signature())
{
api.POST("/authorized", authorizedHandler.Create())
api.GET("/authorized", authorizedHandler.List())
api.PATCH("/authorized/used", authorizedHandler.UpdateUsed())
api.DELETE("/authorized/:id", authorizedHandler.Delete())
api.POST("/authorized_api", authorizedHandler.CreateAPI())
api.GET("/authorized_api", authorizedHandler.ListAPI())
api.DELETE("/authorized_api/:id", authorizedHandler.DeleteAPI())
api.POST("/admin", adminHandler.Create())
api.GET("/admin", adminHandler.List())
api.PATCH("/admin/used", adminHandler.UpdateUsed())
api.PATCH("/admin/reset_password/:id", adminHandler.ResetPassword())
api.DELETE("/admin/:id", adminHandler.Delete())
api.POST("/admin/logout", adminHandler.Logout())
api.PATCH("/admin/modify_password", adminHandler.ModifyPassword())
api.GET("/admin/info", adminHandler.Detail())
api.PATCH("/admin/modify_personal_info", adminHandler.ModifyPersonalInfo())
// tool
toolHandler := tool_handler.New(r.logger, r.db, r.cache)
api.GET("/tool/hashids/encode/:id", toolHandler.HashIdsEncode())
api.GET("/tool/hashids/decode/:id", toolHandler.HashIdsDecode())
}
}
+23
View File
@@ -1,10 +1,13 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/web/controller/admin_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/authorized_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/configinfo_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/dashboard_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/gencode_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/index_handler"
"github.com/xinliangnote/go-gin-api/internal/web/controller/tool_handler"
)
func setWebRouter(r *resource) {
@@ -13,6 +16,9 @@ func setWebRouter(r *resource) {
dashboardHandler := dashboard_handler.New(r.logger, r.db, r.cache)
genCodeHandler := gencode_handler.New(r.logger, r.db, r.cache)
configInfoHandler := configinfo_handler.New(r.logger, r.db, r.cache)
authorizedHandler := authorized_handler.New(r.logger, r.db, r.cache)
toolHandler := tool_handler.New(r.logger, r.db, r.cache)
adminHandler := admin_handler.New(r.logger, r.db, r.cache)
web := r.mux.Group("", r.middles.DisableLog())
{
@@ -35,5 +41,22 @@ func setWebRouter(r *resource) {
web.GET("/handlergen", genCodeHandler.HandlerView())
web.POST("/handlergen_exec", genCodeHandler.HandlerExecute())
// 调用方
web.GET("/authorized/list", authorizedHandler.ListView())
web.GET("/authorized/add", authorizedHandler.AddView())
web.GET("/authorized/api/:id", authorizedHandler.ApiView())
web.GET("/authorized/demo", authorizedHandler.DemoView())
// 管理员
web.GET("/admin/list", adminHandler.ListView())
web.GET("/admin/add", adminHandler.AddView())
web.GET("/admin/modify_password", adminHandler.ModifyPasswordView())
web.GET("/admin/modify_info", adminHandler.ModifyInfoView())
web.GET("/login", adminHandler.LoginView())
// 工具箱
web.GET("/tool/hashids", toolHandler.HashIdsView())
web.GET("/tool/logs", toolHandler.LogsView())
}
}
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) AddView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_add", nil)
}
}
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) ListView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_list", nil)
}
}
@@ -0,0 +1,9 @@
package admin_handler
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (h *handler) LoginView() core.HandlerFunc {
return func(c core.Context) {
c.HTML("admin_login", nil)
}
}

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