未验证 提交 0659feb9 编写于 作者: Mr.奇淼('s avatar Mr.奇淼( 提交者: GitHub

Merge pull request #189 from azunia/own_dev

修改了头像组件 增加了本地上传功能
......@@ -13,9 +13,9 @@
[English](./README.md) | 简体中文
# 项目文档
[在线文档](http://doc.henrongyi.top/)
[在线文档](https://www.gin-vue-admin.com/)
[开发教学](http://doc.henrongyi.top/help/) (贡献者: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>)
[开发教学](https://www.gin-vue-admin.com/docs/help) (贡献者: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>)
- 前端UI框架:[element-ui](https://github.com/ElemeFE/element)
- 后台框架:[gin](https://github.com/gin-gonic/gin)
......@@ -306,7 +306,7 @@ swag init
## 9. 捐赠
如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: [点我](http://doc.henrongyi.top/more/coffee.html)
如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: [点我](https://www.gin-vue-admin.com/docs/coffee)
## 10. 商用注意事项
......
......@@ -13,9 +13,9 @@
English | [简体中文](./README-zh_CN.md)
# Project Guidelines
[Online Documentation](http://doc.henrongyi.top/)
[Online Documentation](https://www.gin-vue-admin.com/)
[Development Steps](http://doc.henrongyi.top/help/) (Contributor: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>)
[Development Steps](https://www.gin-vue-admin.com/docs/help) (Contributor: <a href="https://github.com/LLemonGreen">LLemonGreen</a> And <a href="https://github.com/fkk0509">Fann</a>)
- Web UI Framework:[element-ui](https://github.com/ElemeFE/element)
- Server Framework:[gin](https://github.com/gin-gonic/gin)
......@@ -315,7 +315,7 @@ backend code file: model\dnModel\api.go
## 9. Donate
If you find this project useful, you can buy author a glass of juice :tropical_drink: [here](http://doc.henrongyi.top/more/coffee.html)
If you find this project useful, you can buy author a glass of juice :tropical_drink: [here](https://www.gin-vue-admin.com/docs/coffee)
## 10. Commercial considerations
......
......@@ -2,6 +2,7 @@ package v1
import (
"fmt"
"gin-vue-admin/global"
"gin-vue-admin/global/response"
"gin-vue-admin/model"
"gin-vue-admin/model/request"
......@@ -27,8 +28,17 @@ func UploadFile(c *gin.Context) {
response.FailWithMessage(fmt.Sprintf("上传文件失败,%v", err), c)
} else {
// 文件上传后拿到文件路径
err, filePath, key := utils.Upload(header)
if err != nil {
var uploadErr error
var filePath string
var key string
if global.GVA_CONFIG.LocalUpload.Local {
// 本地上传
uploadErr, filePath, key = utils.UploadFileLocal(header)
} else {
// 七牛云上传
uploadErr, filePath, key = utils.UploadRemote(header)
}
if uploadErr != nil {
response.FailWithMessage(fmt.Sprintf("接收返回值失败,%v", err), c)
} else {
// 修改数据库后得到修改后的user并且返回供前端使用
......
......@@ -52,7 +52,7 @@ func SimpleUploaderUpload(c *gin.Context) {
// @Security ApiKeyAuth
// @Produce application/json
// @Param params md5 get "测试文件是否已经存在和判断已经上传过的切片"
// @Param md5 query string true "测试文件是否已经存在和判断已经上传过的切片"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
// @Router /simpleUploader/checkFileMd5 [get]
func CheckFileMd5(c *gin.Context) {
......@@ -72,7 +72,7 @@ func CheckFileMd5(c *gin.Context) {
// @Summary 合并文件
// @Security ApiKeyAuth
// @Produce application/json
// @Param params md5 get "合并文件"
// @Param md5 query string true "合并文件"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"合并成功"}"
// @Router /simpleUploader/mergeFileMd5 [get]
func MergeFileMd5(c *gin.Context) {
......
......@@ -88,10 +88,12 @@ func tokenNext(c *gin.Context, user model.SysUser) {
UUID: user.UUID,
ID: user.ID,
NickName: user.NickName,
Username: user.Username,
AuthorityId: user.AuthorityId,
BufferTime: 60*60*24, // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失
StandardClaims: jwt.StandardClaims{
NotBefore: time.Now().Unix() - 1000, // 签名生效时间
ExpiresAt: time.Now().Unix() + 60*60*24*7, // 过期时间 一周
ExpiresAt: time.Now().Unix() + 60*60*24*7, // 过期时间 7天
Issuer: "qmPlus", // 签名的发行者
},
}
......@@ -108,11 +110,9 @@ func tokenNext(c *gin.Context, user model.SysUser) {
}, c)
return
}
var loginJwt model.JwtBlacklist
loginJwt.Jwt = token
err, jwtStr := service.GetRedisJWT(user.Username)
if err == redis.Nil {
if err := service.SetRedisJWT(loginJwt, user.Username); err != nil {
if err := service.SetRedisJWT(token, user.Username); err != nil {
response.FailWithMessage("设置登录状态失败", c)
return
}
......@@ -130,7 +130,7 @@ func tokenNext(c *gin.Context, user model.SysUser) {
response.FailWithMessage("jwt作废失败", c)
return
}
if err := service.SetRedisJWT(loginJwt, user.Username); err != nil {
if err := service.SetRedisJWT(jwtStr, user.Username); err != nil {
response.FailWithMessage("设置登录状态失败", c)
return
}
......@@ -195,8 +195,16 @@ func UploadHeaderImg(c *gin.Context) {
response.FailWithMessage(fmt.Sprintf("上传文件失败,%v", err), c)
} else {
// 文件上传后拿到文件路径
err, filePath, _ := utils.Upload(header)
if err != nil {
var uploadErr error
var filePath string
if global.GVA_CONFIG.LocalUpload.Local {
// 本地上传
uploadErr, filePath, _ = utils.UploadAvatarLocal(header)
} else {
// 七牛云上传
uploadErr, filePath, _ = utils.UploadRemote(header)
}
if uploadErr != nil {
response.FailWithMessage(fmt.Sprintf("接收返回值失败,%v", err), c)
} else {
// 修改数据库后得到修改后的user并且返回供前端使用
......
......@@ -2,60 +2,66 @@
# casbin configuration
casbin:
model-path: './resource/rbac_model.conf'
model-path: './resource/rbac_model.conf'
# jwt configuration
jwt:
signing-key: 'qmPlus'
signing-key: 'qmPlus'
# mysql connect configuration
mysql:
username: root
password: 'Aa@6447985'
path: '127.0.0.1:3306'
db-name: 'qmPlus'
config: 'charset=utf8&parseTime=True&loc=Local'
max-idle-conns: 10
max-open-conns: 10
log-mode: false
username: root
password: 'Aa@6447985'
path: '127.0.0.1:3306'
db-name: 'qmPlus'
config: 'charset=utf8mb4&parseTime=True&loc=Local'
max-idle-conns: 10
max-open-conns: 10
log-mode: false
#sqlite 配置
sqlite:
path: db.db
log-mode: true
config: 'loc=Asia/Shanghai'
path: db.db
log-mode: true
config: 'loc=Asia/Shanghai'
# oss configuration
# 切换本地与七牛云上传,分配头像和文件路径
localupload:
local: true
avatar-path: uploads/avatar
file-path: uploads/file
# 请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址
qiniu:
access-key: '25j8dYBZ2wuiy0yhwShytjZDTX662b8xiFguwxzZ'
secret-key: 'pgdbqEsf7ooZh7W3xokP833h3dZ_VecFXPDeG5JY'
bucket: 'qm-plus-img'
img-path: 'http://qmplusimg.henrongyi.top'
access-key: '25j8dYBZ2wuiy0yhwShytjZDTX662b8xiFguwxzZ'
secret-key: 'pgdbqEsf7ooZh7W3xokP833h3dZ_VecFXPDeG5JY'
bucket: 'qm-plus-img'
img-path: 'http://qmplusimg.henrongyi.top'
# redis configuration
redis:
addr: '127.0.0.1:6379'
password: ''
db: 0
addr: '127.0.0.1:6379'
password: ''
db: 0
# system configuration
system:
use-multipoint: false
env: 'public' # Change to "develop" to skip authentication for development mode
addr: 8888
db-type: "mysql" # support mysql/sqlite
use-multipoint: false
env: 'public' # Change to "develop" to skip authentication for development mode
addr: 8888
db-type: "mysql" # support mysql/sqlite
# captcha configuration
captcha:
key-long: 6
img-width: 240
img-height: 80
key-long: 6
img-width: 240
img-height: 80
# logger configuration
log:
prefix: '[GIN-VUE-ADMIN]'
log-file: true
stdout: 'DEBUG'
file: 'DEBUG'
\ No newline at end of file
prefix: '[GIN-VUE-ADMIN]'
log-file: true
stdout: 'DEBUG'
file: 'DEBUG'
\ No newline at end of file
......@@ -10,6 +10,7 @@ type Server struct {
JWT JWT `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
Log Log `mapstructure:"log" json:"log" yaml:"log"`
LocalUpload LocalUpload `mapstructure:"localUpload" json:"localUpload" yaml:"localUpload"`
}
type System struct {
......@@ -43,6 +44,13 @@ type Redis struct {
Password string `mapstructure:"password" json:"password" yaml:"password"`
DB int `mapstructure:"db" json:"db" yaml:"db"`
}
type LocalUpload struct {
Local bool `mapstructure:"local" json:"local" yaml:"local"`
AvatarPath string `mapstructure:"avatar-path" json:"avatarPath" yaml:"avatar-path"`
FilePath string `mapstructure:"file-path" json:"filePath" yaml:"file-path"`
}
type Qiniu struct {
AccessKey string `mapstructure:"access-key" json:"accessKey" yaml:"access-key"`
SecretKey string `mapstructure:"secret-key" json:"secretKey" yaml:"secret-key"`
......
此差异已折叠。
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// 2020-07-07 10:12:31.2264113 +0800 CST m=+0.088765101
// 2020-08-20 11:59:55.622609 +0800 CST m=+0.106561201
package docs
......@@ -1534,6 +1534,99 @@ var doc = `{
}
}
},
"/simpleUploader/checkFileMd5": {
"get": {
"produces": [
"application/json"
],
"parameters": [
{
"type": "string",
"description": "测试文件是否已经存在和判断已经上传过的切片",
"name": "md5",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/simpleUploader/mergeFileMd5": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [
"application/json"
],
"tags": [
"SimpleUploader"
],
"summary": "合并文件",
"parameters": [
{
"type": "string",
"description": "合并文件",
"name": "md5",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"合并成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/simpleUploader/upload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"SimpleUploader"
],
"summary": "断点续传插件版示例",
"parameters": [
{
"type": "file",
"description": "断点续传插件版示例",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"上传成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/sysDictionary/createSysDictionary": {
"post": {
"security": [
......@@ -2002,6 +2095,45 @@ var doc = `{
}
}
},
"/sysOperationRecord/deleteSysOperationRecordByIds": {
"delete": {
"security": [
{
"ApiKeyAuth": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"SysOperationRecord"
],
"summary": "批量删除SysOperationRecord",
"parameters": [
{
"description": "批量删除SysOperationRecord",
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/request.IdsReq"
}
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/sysOperationRecord/findSysOperationRecord": {
"get": {
"security": [
......@@ -2711,6 +2843,9 @@ var doc = `{
"dataTypeLong": {
"type": "string"
},
"dictType": {
"type": "string"
},
"fieldDesc": {
"type": "string"
},
......@@ -2819,6 +2954,12 @@ var doc = `{
"name": {
"type": "string"
},
"parameters": {
"type": "array",
"items": {
"$ref": "#/definitions/model.SysBaseMenuParameter"
}
},
"parentId": {
"type": "string"
},
......@@ -2833,6 +2974,23 @@ var doc = `{
}
}
},
"model.SysBaseMenuParameter": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"sysBaseMenuId": {
"type": "integer"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"model.SysDictionary": {
"type": "object",
"properties": {
......@@ -3070,6 +3228,17 @@ var doc = `{
}
}
},
"request.IdsReq": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"request.PageInfo": {
"type": "object",
"properties": {
......
......@@ -1517,6 +1517,99 @@
}
}
},
"/simpleUploader/checkFileMd5": {
"get": {
"produces": [
"application/json"
],
"parameters": [
{
"type": "string",
"description": "测试文件是否已经存在和判断已经上传过的切片",
"name": "md5",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/simpleUploader/mergeFileMd5": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"produces": [
"application/json"
],
"tags": [
"SimpleUploader"
],
"summary": "合并文件",
"parameters": [
{
"type": "string",
"description": "合并文件",
"name": "md5",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"合并成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/simpleUploader/upload": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"tags": [
"SimpleUploader"
],
"summary": "断点续传插件版示例",
"parameters": [
{
"type": "file",
"description": "断点续传插件版示例",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"上传成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/sysDictionary/createSysDictionary": {
"post": {
"security": [
......@@ -1985,6 +2078,45 @@
}
}
},
"/sysOperationRecord/deleteSysOperationRecordByIds": {
"delete": {
"security": [
{
"ApiKeyAuth": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"SysOperationRecord"
],
"summary": "批量删除SysOperationRecord",
"parameters": [
{
"description": "批量删除SysOperationRecord",
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/request.IdsReq"
}
}
],
"responses": {
"200": {
"description": "{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/sysOperationRecord/findSysOperationRecord": {
"get": {
"security": [
......@@ -2694,6 +2826,9 @@
"dataTypeLong": {
"type": "string"
},
"dictType": {
"type": "string"
},
"fieldDesc": {
"type": "string"
},
......@@ -2802,6 +2937,12 @@
"name": {
"type": "string"
},
"parameters": {
"type": "array",
"items": {
"$ref": "#/definitions/model.SysBaseMenuParameter"
}
},
"parentId": {
"type": "string"
},
......@@ -2816,6 +2957,23 @@
}
}
},
"model.SysBaseMenuParameter": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"sysBaseMenuId": {
"type": "integer"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"model.SysDictionary": {
"type": "object",
"properties": {
......@@ -3053,6 +3211,17 @@
}
}
},
"request.IdsReq": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"request.PageInfo": {
"type": "object",
"properties": {
......
......@@ -177,6 +177,8 @@ definitions:
type: string
dataTypeLong:
type: string
dictType:
type: string
fieldDesc:
type: string
fieldJson:
......@@ -248,6 +250,10 @@ definitions:
type: boolean
name:
type: string
parameters:
items:
$ref: '#/definitions/model.SysBaseMenuParameter'
type: array
parentId:
type: string
path:
......@@ -257,6 +263,17 @@ definitions:
title:
type: string
type: object
model.SysBaseMenuParameter:
properties:
key:
type: string
sysBaseMenuId:
type: integer
type:
type: string
value:
type: string
type: object
model.SysDictionary:
properties:
desc:
......@@ -415,6 +432,13 @@ definitions:
id:
type: number
type: object
request.IdsReq:
properties:
ids:
items:
type: integer
type: array
type: object
request.PageInfo:
properties:
page:
......@@ -1467,6 +1491,63 @@ paths:
summary: 更新菜单
tags:
- menu
/simpleUploader/checkFileMd5:
get:
parameters:
- description: 测试文件是否已经存在和判断已经上传过的切片
in: query
name: md5
required: true
type: string
produces:
- application/json
responses:
"200":
description: '{"success":true,"data":{},"msg":"查询成功"}'
schema:
type: string
/simpleUploader/mergeFileMd5:
get:
parameters:
- description: 合并文件
in: query
name: md5
required: true
type: string
produces:
- application/json
responses:
"200":
description: '{"success":true,"data":{},"msg":"合并成功"}'
schema:
type: string
security:
- ApiKeyAuth: []
summary: 合并文件
tags:
- SimpleUploader
/simpleUploader/upload:
post:
consumes:
- multipart/form-data
parameters:
- description: 断点续传插件版示例
in: formData
name: file
required: true
type: file
produces:
- application/json
responses:
"200":
description: '{"success":true,"data":{},"msg":"上传成功"}'
schema:
type: string
security:
- ApiKeyAuth: []
summary: 断点续传插件版示例
tags:
- SimpleUploader
/sysDictionary/createSysDictionary:
post:
consumes:
......@@ -1755,6 +1836,30 @@ paths:
summary: 删除SysOperationRecord
tags:
- SysOperationRecord
/sysOperationRecord/deleteSysOperationRecordByIds:
delete:
consumes:
- application/json
parameters:
- description: 批量删除SysOperationRecord
in: body
name: data
required: true
schema:
$ref: '#/definitions/request.IdsReq'
type: object
produces:
- application/json
responses:
"200":
description: '{"success":true,"data":{},"msg":"删除成功"}'
schema:
type: string
security:
- ApiKeyAuth: []
summary: 批量删除SysOperationRecord
tags:
- SysOperationRecord
/sysOperationRecord/findSysOperationRecord:
get:
consumes:
......
......@@ -8,12 +8,16 @@ import (
"github.com/gin-gonic/gin"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"net/http"
)
// 初始化总路由
func Routers() *gin.Engine {
var Router = gin.Default()
// 为用户头像和文件提供静态地址
Router.StaticFS(global.GVA_CONFIG.LocalUpload.AvatarPath, http.Dir(global.GVA_CONFIG.LocalUpload.AvatarPath))
Router.StaticFS(global.GVA_CONFIG.LocalUpload.FilePath, http.Dir(global.GVA_CONFIG.LocalUpload.FilePath))
// Router.Use(middleware.LoadTls()) // 打开就能玩https了
global.GVA_LOG.Debug("use middleware logger")
// 跨域
......
......@@ -10,8 +10,8 @@ func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id\"")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
......
......@@ -9,6 +9,7 @@ import (
"gin-vue-admin/service"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"strconv"
"time"
)
......@@ -16,9 +17,6 @@ func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localSstorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
token := c.Request.Header.Get("x-token")
modelToken := model.JwtBlacklist{
Jwt: token,
}
if token == "" {
response.Result(response.ERROR, gin.H{
"reload": true,
......@@ -26,7 +24,7 @@ func JWTAuth() gin.HandlerFunc {
c.Abort()
return
}
if service.IsBlacklist(token, modelToken) {
if service.IsBlacklist(token) {
response.Result(response.ERROR, gin.H{
"reload": true,
}, "您的帐户异地登陆或令牌失效", c)
......@@ -50,6 +48,24 @@ func JWTAuth() gin.HandlerFunc {
c.Abort()
return
}
if claims.ExpiresAt - time.Now().Unix()<claims.BufferTime {
claims.ExpiresAt = time.Now().Unix() + 60*60*24*7
newToken,_ := j.CreateToken(*claims)
newClaims,_ := j.ParseToken(newToken)
c.Header("new-token",newToken)
c.Header("new-expires-at",strconv.FormatInt(newClaims.ExpiresAt,10))
if global.GVA_CONFIG.System.UseMultipoint {
err,RedisJwtToken := service.GetRedisJWT(newClaims.Username)
if err!=nil {
global.GVA_LOG.Error(err)
}else{
service.JsonInBlacklist(model.JwtBlacklist{Jwt: RedisJwtToken})
//当之前的取成功时才进行拉黑操作
}
// 无论如何都要记录当前的活跃状态
_ = service.SetRedisJWT(newToken,newClaims.Username)
}
}
c.Set("claims", claims)
c.Next()
}
......@@ -111,20 +127,20 @@ func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
}
// 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
jwt.TimeFunc = func() time.Time {
return time.Unix(0, 0)
}
token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
return "", err
}
if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
jwt.TimeFunc = time.Now
claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
return j.CreateToken(*claims)
}
return "", TokenInvalid
}
//func (j *JWT) RefreshToken(tokenString string) (string, error) {
// jwt.TimeFunc = func() time.Time {
// return time.Unix(0, 0)
// }
// token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
// return j.SigningKey, nil
// })
// if err != nil {
// return "", err
// }
// if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
// jwt.TimeFunc = time.Now
// claims.StandardClaims.ExpiresAt = time.Now().Unix() + 60*60*24*7
// return j.CreateToken(*claims)
// }
// return "", TokenInvalid
//}
......@@ -9,7 +9,9 @@ import (
type CustomClaims struct {
UUID uuid.UUID
ID uint
Username string
NickName string
AuthorityId string
BufferTime int64
jwt.StandardClaims
}
......@@ -55,6 +55,10 @@ func MergeFileMd5(md5 string, fileName string) (err error) {
//删除切片信息
err = tx.Delete(&model.ExaSimpleUploader{}, "identifier = ? AND is_done = ?", md5, false).Error
// 添加文件信息
if err != nil {
fmt.Println(err)
tx.Rollback()
}
err = tx.Create(&model.ExaSimpleUploader{
Identifier: md5,
IsDone: true,
......
......@@ -3,6 +3,7 @@ package service
import (
"gin-vue-admin/global"
"gin-vue-admin/model"
"time"
)
// @title JsonInBlacklist
......@@ -23,8 +24,8 @@ func JsonInBlacklist(jwtList model.JwtBlacklist) (err error) {
// @param jwtList model.JwtBlacklist
// @return err error
func IsBlacklist(jwt string, jwtList model.JwtBlacklist) bool {
isNotFound := global.GVA_DB.Where("jwt = ?", jwt).First(&jwtList).RecordNotFound()
func IsBlacklist(jwt string) bool {
isNotFound := global.GVA_DB.Where("jwt = ?", jwt).First(&model.JwtBlacklist{}).RecordNotFound()
return !isNotFound
}
......@@ -47,7 +48,9 @@ func GetRedisJWT(userName string) (err error, redisJWT string) {
// @param userName string
// @return err error
func SetRedisJWT(jwtList model.JwtBlacklist, userName string) (err error) {
err = global.GVA_REDIS.Set(userName, jwtList.Jwt, 1000*1000*1000*60*60*24*7).Err()
func SetRedisJWT(jwt string, userName string) (err error) {
// 此处过期时间等于jwt过期时间
timer := 60*60*24*7*time.Second
err = global.GVA_REDIS.Set(userName, jwt, timer).Err()
return err
}
......@@ -158,7 +158,7 @@ func AddMenuAuthority(menus []model.SysBaseMenu, authorityId string) (err error)
func GetMenuAuthority(authorityId string) (err error, menus []model.SysMenu) {
//sql := "SELECT authority_menu.keep_alive,authority_menu.default_menu,authority_menu.created_at,authority_menu.updated_at,authority_menu.deleted_at,authority_menu.menu_level,authority_menu.parent_id,authority_menu.path,authority_menu.`name`,authority_menu.hidden,authority_menu.component,authority_menu.title,authority_menu.icon,authority_menu.sort,authority_menu.menu_id,authority_menu.authority_id FROM authority_menu WHERE authority_menu.authority_id = ? ORDER BY authority_menu.sort ASC"
err = global.GVA_DB.Order("sort", true).Find(&menus).Error
err = global.GVA_DB.Where("authority_id = ? ", authorityId).Order("sort", true).Find(&menus).Error
//err = global.GVA_DB.Raw(sql, authorityId).Scan(&menus).Error
return err, menus
}
package utils
import (
"gin-vue-admin/global"
"io"
"mime/multipart"
"os"
"path"
"strings"
"time"
)
func UploadAvatarLocal(file *multipart.FileHeader) (err error, localPath string, key string) {
// 读取文件后缀
ext := path.Ext(file.Filename)
// 读取文件名并加密
fileName := strings.TrimSuffix(file.Filename, ext)
fileName = MD5V([]byte(fileName))
// 拼接新文件名
lastName := fileName + "_" + time.Now().Format("20060102150405") + ext
// 读取全局变量的定义路径
savePath := global.GVA_CONFIG.LocalUpload.AvatarPath
// 尝试创建此路径
err = os.MkdirAll(savePath, os.ModePerm)
if err != nil{
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
// 拼接路径和文件名
dst := savePath + "/" + lastName
// 下面为上传逻辑
// 打开文件 defer 关闭
src, err := file.Open()
if err != nil {
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
defer src.Close()
// 创建文件 defer 关闭
out, err := os.Create(dst)
if err != nil {
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
defer out.Close()
// 传输(拷贝)文件
_, err = io.Copy(out, src)
if err != nil {
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
return nil, dst, lastName
}
package utils
import (
"gin-vue-admin/global"
"io"
"mime/multipart"
"os"
"path"
"strings"
"time"
)
func UploadFileLocal(file *multipart.FileHeader) (err error, localPath string, key string) {
// 读取文件后缀
ext := path.Ext(file.Filename)
// 读取文件名并加密
fileName := strings.TrimSuffix(file.Filename, ext)
fileName = MD5V([]byte(fileName))
// 拼接新文件名
lastName := fileName + "_" + time.Now().Format("20060102150405") + ext
// 读取全局变量的定义路径
savePath := global.GVA_CONFIG.LocalUpload.FilePath
// 尝试创建此路径
err = os.MkdirAll(savePath, os.ModePerm)
if err != nil{
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
// 拼接路径和文件名
dst := savePath + "/" + lastName
// 下面为上传逻辑
// 打开文件 defer 关闭
src, err := file.Open()
if err != nil {
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
defer src.Close()
// 创建文件 defer 关闭
out, err := os.Create(dst)
if err != nil {
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
defer out.Close()
// 传输(拷贝)文件
_, err = io.Copy(out, src)
if err != nil {
global.GVA_LOG.Error("upload local file fail:", err)
return err, "", ""
}
return nil, dst, lastName
}
......@@ -11,7 +11,7 @@ import (
)
// 接收两个参数 一个文件流 一个 bucket 你的七牛云标准空间的名字
func Upload(file *multipart.FileHeader) (err error, path string, key string) {
func UploadRemote(file *multipart.FileHeader) (err error, path string, key string) {
putPolicy := storage.PutPolicy{
Scope: global.GVA_CONFIG.Qiniu.Bucket,
}
......
此差异已折叠。
......@@ -14,7 +14,7 @@
"echarts": "^4.7.0",
"element-ui": "^2.12.0",
"mavon-editor": "^2.7.7",
"node-sass": "^4.12.0",
"node-sass": "^4.14.1",
"path": "^0.12.7",
"qs": "^6.8.0",
"quill": "^1.3.7",
......
<template>
<span class="headerAvatar">
<template v-if="picType === 'avatar'">
<el-avatar :size="30" :src="avatar" v-if="userInfo.headerImg"></el-avatar>
<el-avatar :size="30" :src="require('@/assets/noBody.png')" v-else></el-avatar>
</template>
<template v-if="picType === 'img'">
<img :src="avatar" class="avatar" v-if="userInfo.headerImg" />
<img :src="require('@/assets/noBody.png')" class="avatar" v-else/>
</template>
<template v-if="picType === 'file'">
<img :src="file" class="file"/>
</template>
</span>
</template>
<script>
import { mapGetters } from 'vuex'
const path = process.env.VUE_APP_BASE_API
export default {
name: "customPic",
props: {
picType: {
type: String,
required: false,
default: "avatar"
},
picSrc: {
type: String,
required: false,
default: ""
}
},
data(){
return{
path: path,
}
},
computed:{
...mapGetters('user', ['userInfo']),
avatar(){
if(this.picSrc === ''){
if(this.userInfo.headerImg !== '' && this.userInfo.headerImg.slice(0, 4) === "http"){
return this.userInfo.headerImg
}
return this.path + this.userInfo.headerImg
}else{
if(this.picSrc !== '' && this.picSrc.slice(0, 4) === "http"){
return this.picSrc
}
return this.path + this.picSrc
}
},
file(){
if(this.picSrc && this.picSrc.slice(0, 4) !== "http"){
return this.path + this.picSrc
}
return this.picSrc
}
}
}
</script>
<style scoped>
.headerAvatar{
display: flex;
justify-content: center;
align-items: center;
}
.file{
width: 80px;
height: 80px;
position: relative;
}
</style>
\ No newline at end of file
......@@ -7,14 +7,6 @@ const whiteList = ['login', 'register']
router.beforeEach(async(to, from, next) => {
const token = store.getters['user/token']
// if (token) {
// const expiresAt = store.getters['user/expiresAt']
// const nowUnix = new Date().getTime()
// const hasExpires = (expiresAt - nowUnix) < 0
// if (hasExpires) {
// store.dispatch['user/claerAll']
// }
// }
// 在白名单中的判断情况
if (whiteList.indexOf(to.name) > -1) {
if (token) {
......
......@@ -11,7 +11,6 @@ export const user = {
authority: "",
},
token: "",
expiresAt: ""
},
mutations: {
setUserInfo(state, userInfo) {
......@@ -22,14 +21,9 @@ export const user = {
// 这里的 `state` 对象是模块的局部状态
state.token = token
},
setExpiresAt(state, expiresAt) {
// 这里的 `state` 对象是模块的局部状态
state.expiresAt = expiresAt
},
LoginOut(state) {
state.userInfo = {}
state.token = ""
state.expiresAt = ""
router.push({ name: 'login', replace: true })
sessionStorage.clear()
window.location.reload()
......@@ -45,7 +39,6 @@ export const user = {
const res = await login(loginInfo)
commit('setUserInfo', res.data.user)
commit('setToken', res.data.token)
commit('setExpiresAt', res.data.expiresAt)
if (res.code == 0) {
const redirect = router.history.current.query.redirect
if (redirect) {
......@@ -69,8 +62,6 @@ export const user = {
token(state) {
return state.token
},
expiresAt(state) {
return state.expiresAt
}
}
}
\ No newline at end of file
......@@ -21,13 +21,13 @@ const showLoading = () => {
}
const closeLoading = () => {
acitveAxios--
if (acitveAxios <= 0) {
clearTimeout(timer)
loadingInstance && loadingInstance.close()
acitveAxios--
if (acitveAxios <= 0) {
clearTimeout(timer)
loadingInstance && loadingInstance.close()
}
}
}
//http request 拦截器
//http request 拦截器
service.interceptors.request.use(
config => {
showLoading()
......@@ -37,7 +37,7 @@ service.interceptors.request.use(
config.headers = {
'Content-Type': 'application/json',
'x-token': token,
'x-user-id':user.ID
'x-user-id': user.ID
}
return config;
},
......@@ -57,6 +57,9 @@ service.interceptors.request.use(
service.interceptors.response.use(
response => {
closeLoading()
if (response.headers["new-token"]) {
store.commit('user/setToken', response.headers["new-token"])
}
if (response.data.code == 0 || response.headers.success === "true") {
return response.data
} else {
......
......@@ -17,12 +17,7 @@
<el-table :data="tableData" border stripe>
<el-table-column label="预览" width="100">
<template slot-scope="scope">
<img
:alt="scope.row.alt"
:src="scope.row.url"
height="80"
width="80"
/>
<CustomPic picType="file" :picSrc="scope.row.url"/>
</template>
</el-table-column>
<el-table-column label="日期" prop="UpdatedAt" width="180">
......@@ -77,9 +72,13 @@ import infoList from "@/components/mixins/infoList";
import { getFileList, deleteFile } from "@/api/fileUploadAndDownload";
import { downloadImage } from "@/utils/downloadImg";
import { formatTimeToStr } from "@/utils/data";
import CustomPic from '@/components/customPic'
export default {
name: "Upload",
mixins: [infoList],
components: {
CustomPic
},
data() {
return {
fullscreenLoading: false,
......
......@@ -31,9 +31,9 @@
<Search />
<Screenfull class="screenfull"></Screenfull>
<el-dropdown>
<span class="el-dropdown-link">
<img :src="userInfo.headerImg" height="30" width="30" />
{{userInfo.title}}
<span class="header-avatar">
欢迎您,<CustomPic/>
<span style="margin-left: 5px">{{userInfo.nickName}}</span>
<i class="el-icon-arrow-down"></i>
</span>
<el-dropdown-menu class="dropdown-group" slot="dropdown">
......@@ -95,6 +95,7 @@ import Search from '@/view/layout/search/search'
import BottomInfo from '@/view/layout/bottomInfo/bottomInfo'
import { mapGetters, mapActions } from 'vuex'
import { changePassword } from '@/api/user'
import CustomPic from '@/components/customPic'
export default {
name: 'Layout',
data() {
......@@ -138,7 +139,8 @@ export default {
HistoryComponent,
Screenfull,
Search,
BottomInfo
BottomInfo,
CustomPic
},
methods: {
...mapActions('user', ['LoginOut']),
......@@ -374,4 +376,9 @@ $mainHight: 100vh;
.screenfull {
display: inline-block;
}
.header-avatar{
display: flex;
justify-content: center;
align-items: center;
}
</style>
......@@ -9,8 +9,7 @@
class="avatar-uploader"
name="headerImg"
>
<img :src="userInfo.headerImg" class="avatar" v-if="userInfo.headerImg" />
<i class="el-icon-plus avatar-uploader-icon" v-else></i>
<CustomPic picType="img"/>
</el-upload>
<!-- <el-avatar :size="120" :src="userInfo.headerImg" shape="square"></el-avatar> -->
......@@ -23,6 +22,7 @@
</div>
</template>
<script>
import CustomPic from '@/components/customPic'
import { mapGetters, mapMutations } from 'vuex'
const path = process.env.VUE_APP_BASE_API
export default {
......@@ -32,6 +32,9 @@ export default {
path:path
}
},
components: {
CustomPic
},
computed: {
...mapGetters('user', ['userInfo', 'token'])
},
......
......@@ -7,7 +7,7 @@
<el-table-column label="头像" min-width="50">
<template slot-scope="scope">
<div :style="{'textAlign':'center'}">
<img :src="scope.row.headerImg" height="35" width="35" />
<CustomPic :picSrc="scope.row.headerImg"/>
</div>
</template>
</el-table-column>
......@@ -106,9 +106,13 @@ import {
import { getAuthorityList } from "@/api/authority";
import infoList from "@/components/mixins/infoList";
import { mapGetters } from "vuex";
import CustomPic from '@/components/customPic'
export default {
name: "Api",
mixins: [infoList],
components: {
CustomPic
},
data() {
return {
listApi: getUserList,
......
<template>
<div class="system">
<el-form :model="config" label-width="100px" ref="form" class="system">
<h2>系统配置</h2>
<el-form-item label="多点登录拦截">
<el-checkbox v-model="config.system.useMultipoint">开启</el-checkbox>
</el-form-item>
<el-form-item label="环境值">
<el-input v-model="config.system.env"></el-input>
</el-form-item>
<el-form-item label="端口值">
<el-input v-model.number="config.system.addr"></el-input>
</el-form-item>
<el-form-item label="数据库类型">
<el-select v-model="config.system.dbType">
<el-option value="sqlite"></el-option>
<el-option value="mysql"></el-option>
</el-select>
</el-form-item>
<h2>jwt签名</h2>
<el-form-item label="jwt签名">
<el-input v-model="config.jwt.signingKey"></el-input>
</el-form-item>
<h2>casbin配置</h2>
<el-form-item label="模型地址">
<el-input v-model="config.casbin.modelPath"></el-input>
</el-form-item>
<template v-show="config.system.dbType == 'mysql'">
<h2>mysql admin数据库配置</h2>
<el-form-item label="username">
<el-input v-model="config.mysql.username"></el-input>
</el-form-item>
<el-form-item label="password">
<el-input v-model="config.mysql.password"></el-input>
</el-form-item>
<el-form-item label="path">
<el-input v-model="config.mysql.path"></el-input>
</el-form-item>
<el-form-item label="dbname">
<el-input v-model="config.mysql.dbname"></el-input>
</el-form-item>
<el-form-item label="maxIdleConns">
<el-input v-model.number="config.mysql.maxIdleConns"></el-input>
</el-form-item>
<el-form-item label="maxOpenConns">
<el-input v-model.number="config.mysql.maxOpenConns"></el-input>
</el-form-item>
<el-form-item label="logMode">
<el-checkbox v-model="config.mysql.logMode"></el-checkbox>
</el-form-item>
</template>
<template v-show="config.system.dbType == 'sqlite'">
<h2>sqlite admin数据库配置</h2>
<el-form-item label="path">
<el-input v-model="config.sqlite.path"></el-input>
</el-form-item>
<el-form-item label="config">
<el-input v-model="config.sqlite.config"></el-input>
</el-form-item>
<el-form-item label="logMode">
<el-checkbox v-model="config.sqlite.logMode"></el-checkbox>
</el-form-item>
</template>
<h2>Redis admin数据库配置</h2>
<el-form-item label="addr">
<el-input v-model="config.redis.addr"></el-input>
</el-form-item>
<el-form-item label="password">
<el-input v-model="config.redis.password"></el-input>
</el-form-item>
<el-form-item label="db">
<el-input v-model="config.redis.db"></el-input>
</el-form-item>
<h2>七牛密钥配置</h2>
<el-form-item label="accessKey">
<el-input v-model="config.qiniu.accessKey"></el-input>
</el-form-item>
<el-form-item label="secretKey">
<el-input v-model="config.qiniu.secretKey"></el-input>
</el-form-item>
<h2>验证码配置</h2>
<el-form-item label="keyLong">
<el-input v-model.number="config.captcha.keyLong"></el-input>
</el-form-item>
<el-form-item label="imgWidth">
<el-input v-model.number="config.captcha.imgWidth"></el-input>
</el-form-item>
<el-form-item label="imgHeight">
<el-input v-model.number="config.captcha.imgHeight"></el-input>
</el-form-item>
<h2>日志配置</h2>
<el-form-item label="prefix">
<el-input v-model.number="config.log.prefix"></el-input>
</el-form-item>
<el-form-item label="logFile">
<el-checkbox v-model="config.log.logFile"></el-checkbox>
</el-form-item>
<el-form-item>
<el-button @click="update" type="primary">立即更新</el-button>
<el-button @click="reload" type="primary">重启服务(开发中)</el-button>
</el-form-item>
</el-form>
</div>
<div class="system">
<el-form :model="config" label-width="100px" ref="form" class="system">
<h2>系统配置</h2>
<el-form-item label="多点登录拦截">
<el-checkbox v-model="config.system.useMultipoint">开启</el-checkbox>
</el-form-item>
<el-form-item label="环境值">
<el-input v-model="config.system.env"></el-input>
</el-form-item>
<el-form-item label="端口值">
<el-input v-model.number="config.system.addr"></el-input>
</el-form-item>
<el-form-item label="数据库类型">
<el-select v-model="config.system.dbType">
<el-option value="sqlite"></el-option>
<el-option value="mysql"></el-option>
</el-select>
</el-form-item>
<h2>jwt签名</h2>
<el-form-item label="jwt签名">
<el-input v-model="config.jwt.signingKey"></el-input>
</el-form-item>
<h2>casbin配置</h2>
<el-form-item label="模型地址">
<el-input v-model="config.casbin.modelPath"></el-input>
</el-form-item>
<template v-show="config.system.dbType == 'mysql'">
<h2>mysql admin数据库配置</h2>
<el-form-item label="username">
<el-input v-model="config.mysql.username"></el-input>
</el-form-item>
<el-form-item label="password">
<el-input v-model="config.mysql.password"></el-input>
</el-form-item>
<el-form-item label="path">
<el-input v-model="config.mysql.path"></el-input>
</el-form-item>
<el-form-item label="dbname">
<el-input v-model="config.mysql.dbname"></el-input>
</el-form-item>
<el-form-item label="maxIdleConns">
<el-input v-model.number="config.mysql.maxIdleConns"></el-input>
</el-form-item>
<el-form-item label="maxOpenConns">
<el-input v-model.number="config.mysql.maxOpenConns"></el-input>
</el-form-item>
<el-form-item label="logMode">
<el-checkbox v-model="config.mysql.logMode"></el-checkbox>
</el-form-item>
</template>
<template v-show="config.system.dbType == 'sqlite'">
<h2>sqlite admin数据库配置</h2>
<el-form-item label="path">
<el-input v-model="config.sqlite.path"></el-input>
</el-form-item>
<el-form-item label="config">
<el-input v-model="config.sqlite.config"></el-input>
</el-form-item>
<el-form-item label="logMode">
<el-checkbox v-model="config.sqlite.logMode"></el-checkbox>
</el-form-item>
</template>
<h2>Redis admin数据库配置</h2>
<el-form-item label="addr">
<el-input v-model="config.redis.addr"></el-input>
</el-form-item>
<el-form-item label="password">
<el-input v-model="config.redis.password"></el-input>
</el-form-item>
<el-form-item label="db">
<el-input v-model="config.redis.db"></el-input>
</el-form-item>
<h2>上传配置</h2>
<el-form-item label="本地或七牛云">
<el-checkbox v-model="config.localUpload.local">本地</el-checkbox>
</el-form-item>
<el-form-item label="本地头像路径">
<el-input v-model="config.localUpload.avatarPath"></el-input>
</el-form-item>
<el-form-item label="本地文件路径">
<el-input v-model="config.localUpload.filePath"></el-input>
</el-form-item>
<h2>七牛密钥配置</h2>
<el-form-item label="accessKey">
<el-input v-model="config.qiniu.accessKey"></el-input>
</el-form-item>
<el-form-item label="secretKey">
<el-input v-model="config.qiniu.secretKey"></el-input>
</el-form-item>
<h2>验证码配置</h2>
<el-form-item label="keyLong">
<el-input v-model.number="config.captcha.keyLong"></el-input>
</el-form-item>
<el-form-item label="imgWidth">
<el-input v-model.number="config.captcha.imgWidth"></el-input>
</el-form-item>
<el-form-item label="imgHeight">
<el-input v-model.number="config.captcha.imgHeight"></el-input>
</el-form-item>
<h2>日志配置</h2>
<el-form-item label="prefix">
<el-input v-model.number="config.log.prefix"></el-input>
</el-form-item>
<el-form-item label="logFile">
<el-checkbox v-model="config.log.logFile"></el-checkbox>
</el-form-item>
<el-form-item>
<el-button @click="update" type="primary">立即更新</el-button>
<el-button @click="reload" type="primary">重启服务(开发中)</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
......@@ -118,7 +128,8 @@ export default {
redis: {},
qiniu: {},
captcha: {},
log: {}
log: {},
localUpload: {}
}
};
},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册