service.xpcool.com/internal/cmd/cmd.go
夏犀麟 4b3c00270f
Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 31s
feat(i18n): 中文化校验提示与服务层错误信息
将 api 层校验规则提示语、service 层 gerror.Wrap 与 response.Error
错误信息、panic 未注册提示统一改为中文,并同步中文化 cmd 路由注释
与变更记录。仅涉及注释、文档与字符串改动,无业务逻辑变更。
2026-09-13 23:22:46 +08:00

176 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
adminctl "service.xpcool.com/internal/controller/admin"
housectl "service.xpcool.com/internal/controller/house"
jobctl "service.xpcool.com/internal/controller/job"
noticectl "service.xpcool.com/internal/controller/notice"
openctl "service.xpcool.com/internal/controller/open"
recruitmentctl "service.xpcool.com/internal/controller/recruitment"
serversecurityctl "service.xpcool.com/internal/controller/serversecurity"
userctl "service.xpcool.com/internal/controller/user"
"service.xpcool.com/internal/library/crypto"
"service.xpcool.com/internal/library/jwt"
"service.xpcool.com/internal/middleware"
admin "service.xpcool.com/internal/service/admin/admin/admin"
adminauth "service.xpcool.com/internal/service/admin/admin/login"
log "service.xpcool.com/internal/service/admin/base/log"
adminaudit "service.xpcool.com/internal/service/admin/system/log"
adminloginlog "service.xpcool.com/internal/service/admin/system/login_log"
adminmenu "service.xpcool.com/internal/service/admin/system/menu"
menu_manage "service.xpcool.com/internal/service/admin/system/menu_manage"
role "service.xpcool.com/internal/service/admin/system/role"
housecommunity "service.xpcool.com/internal/service/house/community"
housedashboard "service.xpcool.com/internal/service/house/dashboard"
houselisting "service.xpcool.com/internal/service/house/listing"
housepresale "service.xpcool.com/internal/service/house/presale"
housetransaction "service.xpcool.com/internal/service/house/transaction"
jobsvc "service.xpcool.com/internal/service/job"
noticesvc "service.xpcool.com/internal/service/notice"
recruitmentsvc "service.xpcool.com/internal/service/recruitment"
serversecuritysvc "service.xpcool.com/internal/service/serversecurity"
userauth "service.xpcool.com/internal/service/user/auth"
)
// 开发环境默认值任何启动方式GoLand / 命令行 / go run漏注入环境变量时兜底
// 避免 ${DB_DSN} 占位符原样传入 gdb 导致全站接口 500。
// 生产GF_GCFG_ENV=prod不启用兜底配置缺失应显性报错。
//
// 注意:库名以本机实际建库为准(本项目为 service / recruitment
// 若你的本地库名或账号口令不同,请通过 DB_DSN / RECRUITMENT_DB_DSN 覆盖。
const (
devDefaultDBDSN = "mysql:service:3103ed27e98fc13b6d60e6571c4a8f76@tcp(127.0.0.1:3306)/service?loc=Local"
devDefaultRecruitmentDSN = "mysql:service:3103ed27e98fc13b6d60e6571c4a8f76@tcp(127.0.0.1:3306)/recruitment?loc=Local"
devDefaultJWTSecret = "dev-only-secret-not-for-production"
)
// setIfEmpty 仅当 env 存在时写入配置优先级env > 配置文件默认值)。
func setIfEmpty(adapter *gcfg.AdapterFile, key, envName string) {
if v := genv.Get(envName); !v.IsEmpty() {
_ = adapter.Set(key, v.String())
}
}
// injectEnv 手动把关键环境变量写入配置系统。
// 注意gf v2.10.2 起配置不再自动替换 ${ENV} 占位符,需在此显式注入,
// 否则 config.dev.yaml 中的 ${DB_DSN}/${JWT_SECRET} 会原样传给数据库与 JWT。
// 开发环境下若 env 缺失,回填本地默认值,保证「零配置可启动」。
func injectEnv(ctx context.Context) {
adapter, ok := g.Cfg().GetAdapter().(*gcfg.AdapterFile)
if !ok {
return
}
isProd := genv.Get("GF_GCFG_ENV").String() == "prod"
if !isProd {
// 开发兜底:仅当配置项仍是未解析的 ${...} 占位符时才写默认值,
// 不覆盖 .env.dev / GoLand 运行配置注入的真实值。
if v, _ := adapter.Get(ctx, "database.default.link"); v != nil && gstr.Contains(gconv.String(v), "${") {
_ = adapter.Set("database.default.link", devDefaultDBDSN)
}
if v, _ := adapter.Get(ctx, "database.recruitment.link"); v != nil && gstr.Contains(gconv.String(v), "${") {
_ = adapter.Set("database.recruitment.link", devDefaultRecruitmentDSN)
}
if v, _ := adapter.Get(ctx, "jwt.secret"); v != nil && gstr.Contains(gconv.String(v), "${") {
_ = adapter.Set("jwt.secret", devDefaultJWTSecret)
}
}
setIfEmpty(adapter, "database.default.link", "DB_DSN")
setIfEmpty(adapter, "jwt.secret", "JWT_SECRET")
// 招聘模块独立数据库与 Bark 推送配置(自建 Bark 服务)。
setIfEmpty(adapter, "database.recruitment.link", "RECRUITMENT_DB_DSN")
setIfEmpty(adapter, "bark.baseUrl", "BARK_BASE_URL")
setIfEmpty(adapter, "bark.deviceKey", "BARK_DEVICE_KEY")
setIfEmpty(adapter, "bark.pushTime", "BARK_PUSH_TIME")
// 服务器安全日志上报令牌(宿主机采集脚本携带,接口侧校验)。
setIfEmpty(adapter, "internalToken", "INTERNAL_TOKEN")
// 登录加密开关(直接跑二进制始终加载 config.yaml需 env 显式注入):
// ENCRYPT_FULL_BODY=true 启用全量请求/响应加密生产ENCRYPT_ALLOW_PLAIN=true 仅开发联调。
setIfEmpty(adapter, "encrypt.fullBody", "ENCRYPT_FULL_BODY")
setIfEmpty(adapter, "encrypt.allowPlain", "ENCRYPT_ALLOW_PLAIN")
}
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
injectEnv(ctx)
s := g.Server()
tokens := jwt.New(ctx)
// 登录密码混合加密服务RSA + AES-GCM私钥加载/生成失败属致命错误,直接终止启动。
cryptoSvc, err := crypto.New(ctx)
if err != nil {
panic(err)
}
userauth.RegisterUserAuth(userauth.NewUserAuth(tokens, nil, nil))
adminauth.RegisterAdminAuth(adminauth.NewAdminAuth(tokens, cryptoSvc))
adminmenu.RegisterAdminMenu(adminmenu.NewAdminMenu())
admin.RegisterAdminManage(admin.NewAdminManage())
role.RegisterRoleManage(role.NewRoleManage())
menu_manage.RegisterMenuManage(menu_manage.NewMenuManage())
log.RegisterLogManage(log.NewLogManage())
adminaudit.RegisterAdminAudit(adminaudit.NewAdminAudit())
adminloginlog.RegisterAdminLoginLog(adminloginlog.NewAdminLoginLog())
housecommunity.RegisterCommunity(housecommunity.NewCommunity())
houselisting.RegisterListing(houselisting.NewListing())
housedashboard.RegisterDashboard(housedashboard.NewDashboard())
housetransaction.RegisterTransaction(housetransaction.NewTransaction())
housepresale.RegisterPresale(housepresale.NewPresale())
recruitmentsvc.RegisterRecruitment(recruitmentsvc.New())
noticesvc.RegisterNotice(noticesvc.New())
jobsvc.RegisterJob(jobsvc.New())
serversecuritysvc.RegisterServerSecurity(serversecuritysvc.New())
s.Group("/api/service/open", func(group *ghttp.RouterGroup) {
group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse)
group.Bind(openctl.New()) // 开放工具接口(前端调用,免鉴权)。
group.Bind(serversecurityctl.NewReport()) // 安全日志上报(宿主机脚本,内部令牌校验)。
})
s.Group("/api/service/user", func(group *ghttp.RouterGroup) {
group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse)
group.Bind(userctl.New()) // 登录与刷新令牌接口公开。
group.Group("/", func(protected *ghttp.RouterGroup) { protected.Middleware(middleware.UserAuth(tokens)) })
})
s.Group("/api/service/admin", func(group *ghttp.RouterGroup) {
// 全量请求/响应加密(生产 encrypt.fullBody=trueCORS → APICrypto → Recover → HandlerResponse。
// Recover 置于 APICrypto 内层,使 500 异常响应同样被加密public-key 端点明文豁免。
group.Middleware(middleware.CORS, middleware.APICrypto(cryptoSvc), middleware.Recover, ghttp.MiddlewareHandlerResponse)
group.Bind(adminctl.NewAuth()) // 公开端点:仅登录。
group.Group("/", func(profile *ghttp.RouterGroup) {
// 仅登录端点:个人资料 / 权限码 / 菜单路由。
profile.Middleware(middleware.AdminAuthOnly(tokens))
profile.Bind(adminctl.NewProfile())
})
group.Group("/", func(protected *ghttp.RouterGroup) {
// 受权限保护端点RBAC 管理、日志等。
// 权限由后端按「方法+路径」自动匹配,无需前端传 X-Permission。
protected.Middleware(middleware.AdminAuth(tokens, adminauth.AdminAuth().PermissionForPath, adminauth.AdminAuth().HasPermission, func(ctx context.Context, id uint64, permission, method, path, ip, param string, duration, status int, userAgent, errorMessage string) {
adminaudit.AdminAudit().Record(ctx, adminaudit.AuditEvent{AdminID: id, Permission: permission, Method: method, Path: path, IP: ip, Param: param, DurationMS: duration, StatusCode: status, ErrorMessage: errorMessage, UserAgent: userAgent})
}))
protected.Bind(adminctl.New())
protected.Bind(housectl.New())
protected.Bind(recruitmentctl.New())
protected.Bind(noticectl.New())
protected.Bind(jobctl.New())
protected.Bind(serversecurityctl.NewManage())
})
})
// 启动自动任务调度器:招聘模块先把任务注册进来,再由 job 模块按 DB 配置统一调度。
recruitmentsvc.RegisterTasks()
jobsvc.Job().StartScheduler(ctx)
s.Run()
return nil
},
}
)