Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions server/core/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package core

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/spf13/cobra"
)

const (
configFlagShort = "-c"
configFlagLong = "--config"
)

func runCli() error {
var configPath string
var resetPasswordUsername string

rootCmd := &cobra.Command{
Use: filepath.Base(os.Args[0]),
Short: "gva",
Example: fmt.Sprintf("%s -c config.yaml --reset-password admin", filepath.Base(os.Args[0])),
SilenceUsage: true,
SilenceErrors: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if resetPasswordUsername != "" {
return resetPasswordByUsername(resetPasswordUsername)
}
return cmd.Help()
},
}

rootCmd.Flags().StringVarP(&configPath, "config", "c", "", "choose config file.")
rootCmd.Flags().StringVar(&resetPasswordUsername, "reset-password", "", "reset the specified user's password and print the new password")

return rootCmd.Execute()
}

func resetPasswordByUsername(username string) error {
username = strings.TrimSpace(username)
if username == "" {
return errors.New("用户名不能为空")
}
if global.GVA_DB == nil {
return errors.New("db not init")
}

password, err := utils.RandomPassword(16)
if err != nil {
return err
}
if err = system.UserServiceApp.ResetPasswordByUsername(username, password); err != nil {
return err
}
fmt.Printf("++++++++++++++++++++++++++++++++++\n")
fmt.Printf("the new password is: %s\n", password)
fmt.Printf("++++++++++++++++++++++++++++++++++\n")
return nil
}

func hasCommandArgs(args []string) bool {
return len(filterCommonArgs(args)) > 0
}

func filterCommonArgs(args []string) []string {
filtered := make([]string, 0, len(args))
for i := 0; i < len(args); i++ {
arg := args[i]
switch {
case arg == configFlagShort || arg == configFlagLong:
if i+1 < len(args) {
i++
Comment on lines +78 to +79

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filterCommonArgs silently drops "-c/--config" even when it is provided without a value (e.g., server -c), which can make RunServer incorrectly treat the invocation as “no command args” and start the HTTP server instead of surfacing an error. Consider detecting the missing value case here (or earlier) and returning an explicit error/exit so mis-specified config flags don’t get ignored.

Suggested change
if i+1 < len(args) {
i++
if i+1 < len(args) {
// Skip the next argument, which should be the config path.
i++
} else {
// Config flag provided without a value: surface an explicit error
// instead of silently dropping the flag and continuing.
fmt.Fprintln(os.Stderr, "error: missing value for -c/--config flag")
os.Exit(1)

Copilot uses AI. Check for mistakes.
}
case strings.HasPrefix(arg, configFlagShort+"="), strings.HasPrefix(arg, configFlagLong+"="):
continue
default:
filtered = append(filtered, arg)
}
}
return filtered
}

func lookupConfigPathArg(args []string) (string, bool) {
for i := 0; i < len(args); i++ {
arg := args[i]
switch {
case arg == configFlagShort || arg == configFlagLong:
if i+1 < len(args) {
return args[i+1], true
}
return "", false

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lookupConfigPathArg returns ("", false) when "-c/--config" is present but missing its value (e.g., server -c). That causes getConfigPath to fall back to env/default config instead of failing fast, which is a behavior change from the previous flag-based parsing (which errors on missing values). Please treat this as an invalid invocation and return a detectable signal (or plumb an error) so the process can exit with a clear message.

Suggested change
return "", false
// Flag present but missing its value: signal presence (true) with empty path
return "", true

Copilot uses AI. Check for mistakes.
case strings.HasPrefix(arg, configFlagShort+"="):
return strings.TrimPrefix(arg, configFlagShort+"="), true
case strings.HasPrefix(arg, configFlagLong+"="):
return strings.TrimPrefix(arg, configFlagLong+"="), true
}
}
return "", false
}
18 changes: 16 additions & 2 deletions server/core/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package core

import (
"fmt"
"os"
"time"

"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/initialize"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
"go.uber.org/zap"
"time"
)

func RunServer() {
func runHttpServer() {
if global.GVA_CONFIG.System.UseRedis {
// 初始化redis服务
initialize.Redis()
Expand Down Expand Up @@ -52,3 +54,15 @@ func RunServer() {
`, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
initServer(address, Router, 10*time.Minute, 10*time.Minute)
}

func RunServer() {
if !hasCommandArgs(os.Args[1:]) {
runHttpServer()
return
}

if err := runCli(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
5 changes: 0 additions & 5 deletions server/core/server_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import (
"go.uber.org/zap"
)

type server interface {
ListenAndServe() error
Shutdown(context.Context) error
}

// initServer 启动服务并实现优雅关闭
func initServer(address string, router *gin.Engine, readTimeout, writeTimeout time.Duration) {
// 创建服务
Expand Down
7 changes: 2 additions & 5 deletions server/core/viper.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package core

import (
"flag"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -43,10 +42,8 @@ func Viper() *viper.Viper {

// getConfigPath 获取配置文件路径, 优先级: 命令行 > 环境变量 > 默认值
func getConfigPath() (config string) {
// `-c` flag parse
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
if config != "" { // 命令行参数不为空 将值赋值于config
if cliConfig, ok := lookupConfigPathArg(os.Args[1:]); ok && cliConfig != "" {
config = cliConfig
fmt.Printf("您正在使用命令行的 '-c' 参数传递的值, config 的路径为 %s\n", config)
return
Comment on lines +45 to 48

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getConfigPath prints the selected config path to stdout. In CLI mode (--reset-password), this will be mixed into stdout along with the “new password” output, which can break scripts that expect stdout to contain only the password block. Consider sending these informational messages to stderr / logger, or suppressing them when running CLI subcommands.

Copilot uses AI. Check for mistakes.
}
Expand Down
4 changes: 3 additions & 1 deletion server/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
Expand Down Expand Up @@ -166,7 +167,8 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/cobra v1.10.2 // indirect

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go.mod marks github.com/spf13/cobra as "// indirect", but it is imported directly by server/core/cli.go. Running go mod tidy (or removing the indirect marker) will keep dependency metadata accurate and avoid confusion about which deps are direct vs indirect.

Suggested change
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/cobra v1.10.2

Copilot uses AI. Check for mistakes.
github.com/spf13/pflag v1.0.9 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/thoas/go-funk v0.7.0 // indirect
Expand Down
9 changes: 9 additions & 0 deletions server/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -289,6 +290,8 @@ github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
Expand Down Expand Up @@ -442,6 +445,7 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
Expand All @@ -465,8 +469,12 @@ github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -553,6 +561,7 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
Expand Down
17 changes: 17 additions & 0 deletions server/service/system/sys_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@ func (userService *UserService) FindUserByUuid(uuid string) (user *system.SysUse
return &u, nil
}

func (userService *UserService) ResetPasswordByUsername(username, password string) (err error) {
if global.GVA_DB == nil {
return fmt.Errorf("db not init")
}

var user system.SysUser
Comment on lines +327 to +332

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new public method is missing the file’s standard function header annotations (e.g., //@author, //@function, //@description) that are present on surrounding UserService methods. Please add the same style of comment block for ResetPasswordByUsername to keep documentation generation/consistency intact.

Copilot uses AI. Check for mistakes.
err = global.GVA_DB.Select("id").Where("username = ?", username).First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("用户不存在")
}
if err != nil {
return err
}

return userService.ResetPassword(user.ID, password)
}

//@author: [piexlmax](https://github.com/piexlmax)
//@function: ResetPassword
//@description: 修改用户密码
Expand Down
26 changes: 26 additions & 0 deletions server/utils/password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package utils

import (
"crypto/rand"
"errors"
)

const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789+-@!"

func RandomPassword(length int) (string, error) {
if length <= 0 {
return "", errors.New("password length must be greater than 0")
}
Comment on lines +10 to +13

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing unit tests under server/utils (e.g., json_test.go, human_duration_test.go), but RandomPassword is new and currently untested. Please add tests for error handling (length <= 0) and basic properties (returned length matches input; output only contains characters from the allowed alphabet).

Copilot uses AI. Check for mistakes.

buf := make([]byte, length)
if _, err := rand.Read(buf); err != nil {
return "", err
}

password := make([]byte, length)
for i, b := range buf {
password[i] = passwordAlphabet[int(b)%len(passwordAlphabet)]
}
Comment on lines +20 to +23

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RandomPassword maps random bytes to the alphabet using modulo (b % len(passwordAlphabet)), which introduces modulo bias and makes some characters slightly more likely. For password generation, prefer unbiased selection (e.g., crypto/rand.Int with a big.Int bound, or rejection sampling) so each character is uniformly distributed.

Copilot uses AI. Check for mistakes.

return string(password), nil
}
Loading