- 新增 internal/library/crypto:RSA-OAEP(SHA-256)+AES-256-GCM 混合解密, 私钥按 配置PEM > data/crypto/rsa_private.pem > 自动生成 三级来源,公钥 SPKI 输出 - 公开接口 POST /system/auth/public-key;LoginReq 支持 encryptedKey/encryptedData - AdminCreate/AdminResetPwd 密码字段支持加密传输(allowPlain 仅开发联调) - 配置 encrypt.privateKey/allowPlain;data/ 加入 gitignore 防止私钥入库
167 lines
8.3 KiB
Go
167 lines
8.3 KiB
Go
package cmd
|
||
|
||
import (
|
||
"context"
|
||
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
"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"
|
||
|
||
adminctl "service.xpcool.com/internal/controller/admin"
|
||
housectl "service.xpcool.com/internal/controller/house"
|
||
openctl "service.xpcool.com/internal/controller/open"
|
||
recruitmentctl "service.xpcool.com/internal/controller/recruitment"
|
||
noticectl "service.xpcool.com/internal/controller/notice"
|
||
jobctl "service.xpcool.com/internal/controller/job"
|
||
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"
|
||
userauth "service.xpcool.com/internal/service/user/auth"
|
||
housecommunity "service.xpcool.com/internal/service/house/community"
|
||
housedashboard "service.xpcool.com/internal/service/house/dashboard"
|
||
houselisting "service.xpcool.com/internal/service/house/listing"
|
||
housetransaction "service.xpcool.com/internal/service/house/transaction"
|
||
housepresale "service.xpcool.com/internal/service/house/presale"
|
||
recruitmentsvc "service.xpcool.com/internal/service/recruitment"
|
||
noticesvc "service.xpcool.com/internal/service/notice"
|
||
jobsvc "service.xpcool.com/internal/service/job"
|
||
serversecuritysvc "service.xpcool.com/internal/service/serversecurity"
|
||
)
|
||
|
||
// 开发环境默认值:任何启动方式(GoLand / 命令行 / go run)漏注入环境变量时兜底,
|
||
// 避免 ${DB_DSN} 占位符原样传入 gdb 导致全站接口 500。
|
||
// 生产(GF_GCFG_ENV=prod)不启用兜底,配置缺失应显性报错。
|
||
const (
|
||
devDefaultDBDSN = "mysql:root:root123@tcp(127.0.0.1:3306)/service_xpcool_com?loc=Local"
|
||
devDefaultRecruitmentDSN = "mysql:root:root123@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")
|
||
}
|
||
|
||
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()) // Open tools API for frontends, no auth required.
|
||
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()) // Login and refresh routes are public.
|
||
group.Group("/", func(protected *ghttp.RouterGroup) { protected.Middleware(middleware.UserAuth(tokens)) })
|
||
})
|
||
s.Group("/api/service/admin", func(group *ghttp.RouterGroup) {
|
||
group.Middleware(middleware.Recover, middleware.CORS, ghttp.MiddlewareHandlerResponse)
|
||
group.Bind(adminctl.NewAuth()) // Public: login only.
|
||
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
|
||
},
|
||
}
|
||
)
|