feat: add member URL pool and login tracking
This commit is contained in:
@@ -3,12 +3,12 @@
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package consts
|
||||
|
||||
// 消息队列
|
||||
const (
|
||||
QueueLogTopic = `request_log` // 访问日志
|
||||
QueueLoginLogTopic = `login_log` // 登录日志
|
||||
QueueServeLogTopic = `serve_log` // 服务日志
|
||||
QueueLogTopic = `request_log` // 访问日志
|
||||
QueueLoginLogTopic = `login_log` // 登录日志
|
||||
QueueAppMemberLoginLogTopic = `app_member_login_log` // C端会员登录日志
|
||||
QueueServeLogTopic = `serve_log` // 服务日志
|
||||
)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package consts
|
||||
|
||||
import (
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
UrlPoolStatusEnabled = 1
|
||||
UrlPoolStatusInvalid = 2
|
||||
|
||||
AppMemberLoginTypePassword = 1
|
||||
AppMemberLoginTypeSms = 2
|
||||
|
||||
AppMemberDomainBoundEnabled = 1
|
||||
AppMemberDomainUnbound = 2
|
||||
AppMemberDomainBoundInvalid = 3
|
||||
)
|
||||
|
||||
var (
|
||||
UrlPoolStatusOptions = []*model.Option{
|
||||
dict.GenSuccessOption(UrlPoolStatusEnabled, "启用"),
|
||||
dict.GenWarningOption(UrlPoolStatusInvalid, "失效"),
|
||||
}
|
||||
AppMemberLoginTypeOptions = []*model.Option{
|
||||
dict.GenPrimaryOption(AppMemberLoginTypePassword, "账号密码"),
|
||||
dict.GenInfoOption(AppMemberLoginTypeSms, "手机验证码"),
|
||||
}
|
||||
AppMemberDomainStatusOptions = []*model.Option{
|
||||
dict.GenSuccessOption(AppMemberDomainBoundEnabled, "已绑定"),
|
||||
dict.GenWarningOption(AppMemberDomainUnbound, "待绑定"),
|
||||
dict.GenErrorOption(AppMemberDomainBoundInvalid, "域名已失效"),
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
dict.RegisterEnums("urlPoolStatus", "URL地址状态", UrlPoolStatusOptions)
|
||||
dict.RegisterEnums("appMemberLoginTypeOptions", "会员登录方式", AppMemberLoginTypeOptions)
|
||||
dict.RegisterEnums("appMemberDomainStatus", "会员域名绑定状态", AppMemberDomainStatusOptions)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"hotgo/api/admin/appmemberloginlog"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
var AppMemberLoginLog = cAppMemberLoginLog{}
|
||||
|
||||
type cAppMemberLoginLog struct{}
|
||||
|
||||
func (c *cAppMemberLoginLog) List(ctx context.Context, req *appmemberloginlog.ListReq) (res *appmemberloginlog.ListRes, err error) {
|
||||
list, total, err := service.SysAppMemberLoginLog().List(ctx, &req.AppMemberLoginLogListInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if list == nil {
|
||||
list = []*sysin.AppMemberLoginLogListModel{}
|
||||
}
|
||||
res = new(appmemberloginlog.ListRes)
|
||||
res.List = list
|
||||
res.PageRes.Pack(req, total)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cAppMemberLoginLog) View(ctx context.Context, req *appmemberloginlog.ViewReq) (*appmemberloginlog.ViewRes, error) {
|
||||
data, err := service.SysAppMemberLoginLog().View(ctx, &req.AppMemberLoginLogViewInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &appmemberloginlog.ViewRes{AppMemberLoginLogViewModel: data}, nil
|
||||
}
|
||||
|
||||
func (c *cAppMemberLoginLog) Export(ctx context.Context, req *appmemberloginlog.ExportReq) (*appmemberloginlog.ExportRes, error) {
|
||||
return &appmemberloginlog.ExportRes{}, service.SysAppMemberLoginLog().Export(ctx, &req.AppMemberLoginLogListInp)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"hotgo/api/admin/urlpool"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
var UrlPool = cUrlPool{}
|
||||
|
||||
type cUrlPool struct{}
|
||||
|
||||
func (c *cUrlPool) List(ctx context.Context, req *urlpool.ListReq) (res *urlpool.ListRes, err error) {
|
||||
list, total, err := service.SysUrlPool().List(ctx, &req.UrlPoolListInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if list == nil {
|
||||
list = []*sysin.UrlPoolListModel{}
|
||||
}
|
||||
res = new(urlpool.ListRes)
|
||||
res.List = list
|
||||
res.PageRes.Pack(req, total)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cUrlPool) View(ctx context.Context, req *urlpool.ViewReq) (*urlpool.ViewRes, error) {
|
||||
data, err := service.SysUrlPool().View(ctx, &req.UrlPoolViewInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &urlpool.ViewRes{UrlPoolViewModel: data}, nil
|
||||
}
|
||||
|
||||
func (c *cUrlPool) Edit(ctx context.Context, req *urlpool.EditReq) (*urlpool.EditRes, error) {
|
||||
return &urlpool.EditRes{}, service.SysUrlPool().Edit(ctx, &req.UrlPoolEditInp)
|
||||
}
|
||||
|
||||
func (c *cUrlPool) Status(ctx context.Context, req *urlpool.StatusReq) (*urlpool.StatusRes, error) {
|
||||
return &urlpool.StatusRes{}, service.SysUrlPool().Status(ctx, &req.UrlPoolStatusInp)
|
||||
}
|
||||
|
||||
func (c *cUrlPool) Import(ctx context.Context, req *urlpool.ImportReq) (*urlpool.ImportRes, error) {
|
||||
if req.File == nil {
|
||||
return nil, gerror.New("请选择Excel文件")
|
||||
}
|
||||
filename := strings.ToLower(strings.TrimSpace(req.File.Filename))
|
||||
if !strings.HasSuffix(filename, ".xlsx") && !strings.HasSuffix(filename, ".xlsm") {
|
||||
return nil, gerror.New("仅支持.xlsx或.xlsm格式的Excel文件")
|
||||
}
|
||||
if req.File.Size > 10*1024*1024 {
|
||||
return nil, gerror.New("Excel文件不能超过10MB")
|
||||
}
|
||||
file, err := req.File.Open()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "读取Excel文件失败")
|
||||
}
|
||||
defer file.Close()
|
||||
result, err := service.SysUrlPool().Import(ctx, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &urlpool.ImportRes{UrlPoolImportModel: result}, nil
|
||||
}
|
||||
|
||||
func (c *cUrlPool) MemberList(ctx context.Context, req *urlpool.MemberListReq) (res *urlpool.MemberListRes, err error) {
|
||||
list, total, err := service.SysUrlPool().MemberList(ctx, &req.UrlPoolMemberListInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if list == nil {
|
||||
list = []*sysin.UrlPoolMemberListModel{}
|
||||
}
|
||||
res = new(urlpool.MemberListRes)
|
||||
res.List = list
|
||||
res.PageRes.Pack(req, total)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cUrlPool) Summary(ctx context.Context, req *urlpool.SummaryReq) (*urlpool.SummaryRes, error) {
|
||||
data, err := service.SysUrlPool().Summary(ctx, &req.UrlPoolSummaryInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &urlpool.SummaryRes{UrlPoolSummaryModel: data}, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package member
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "hotgo/api/api/member/v1"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (*v1.RegisterRes, error) {
|
||||
data, err := service.SysAppMemberAuth().Register(ctx, &req.AppMemberRegisterInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.RegisterRes{AppMemberRegisterModel: data}, nil
|
||||
}
|
||||
|
||||
func (c *ControllerV1) AccountLogin(ctx context.Context, req *v1.AccountLoginReq) (*v1.AccountLoginRes, error) {
|
||||
data, err := service.SysAppMemberAuth().AccountLogin(ctx, &req.AppMemberAccountLoginInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.AccountLoginRes{AppMemberLoginModel: data}, nil
|
||||
}
|
||||
|
||||
func (c *ControllerV1) MobileLogin(ctx context.Context, req *v1.MobileLoginReq) (*v1.MobileLoginRes, error) {
|
||||
data, err := service.SysAppMemberAuth().MobileLogin(ctx, &req.AppMemberMobileLoginInp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v1.MobileLoginRes{AppMemberLoginModel: data}, nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// appMemberLoginLogDao is the data access object for the table hg_app_member_login_log.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type appMemberLoginLogDao struct {
|
||||
*internal.AppMemberLoginLogDao
|
||||
}
|
||||
|
||||
var (
|
||||
// AppMemberLoginLog is a globally accessible object for table hg_app_member_login_log operations.
|
||||
AppMemberLoginLog = appMemberLoginLogDao{internal.NewAppMemberLoginLogDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,109 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// AppMemberLoginLogDao is the data access object for the table hg_app_member_login_log.
|
||||
type AppMemberLoginLogDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns AppMemberLoginLogColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// AppMemberLoginLogColumns defines and stores column names for the table hg_app_member_login_log.
|
||||
type AppMemberLoginLogColumns struct {
|
||||
Id string // 日志ID
|
||||
TenantId string // 租户ID
|
||||
ReqId string // 请求ID
|
||||
MemberId string // 会员ID,未识别会员时为0
|
||||
Username string // 用户名快照
|
||||
MobileMask string // 手机号脱敏值
|
||||
LoginType string // 登录方式:1账号密码,2手机验证码
|
||||
Status string // 登录状态:1成功,2失败
|
||||
ErrMsg string // 失败原因
|
||||
LoginAt string // 登录时间
|
||||
LoginIpCipher string // 登录IP密文
|
||||
LoginIpMask string // 登录IP脱敏值
|
||||
LoginRegion string // 登录地区
|
||||
LoginDomain string // 登录页面标准化域名
|
||||
UserAgent string // 浏览器UA
|
||||
CreatedAt string // 创建时间
|
||||
}
|
||||
|
||||
// appMemberLoginLogColumns holds the columns for the table hg_app_member_login_log.
|
||||
var appMemberLoginLogColumns = AppMemberLoginLogColumns{
|
||||
Id: "id",
|
||||
TenantId: "tenant_id",
|
||||
ReqId: "req_id",
|
||||
MemberId: "member_id",
|
||||
Username: "username",
|
||||
MobileMask: "mobile_mask",
|
||||
LoginType: "login_type",
|
||||
Status: "status",
|
||||
ErrMsg: "err_msg",
|
||||
LoginAt: "login_at",
|
||||
LoginIpCipher: "login_ip_cipher",
|
||||
LoginIpMask: "login_ip_mask",
|
||||
LoginRegion: "login_region",
|
||||
LoginDomain: "login_domain",
|
||||
UserAgent: "user_agent",
|
||||
CreatedAt: "created_at",
|
||||
}
|
||||
|
||||
// NewAppMemberLoginLogDao creates and returns a new DAO object for table data access.
|
||||
func NewAppMemberLoginLogDao(handlers ...gdb.ModelHandler) *AppMemberLoginLogDao {
|
||||
return &AppMemberLoginLogDao{
|
||||
group: "default",
|
||||
table: "hg_app_member_login_log",
|
||||
columns: appMemberLoginLogColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *AppMemberLoginLogDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *AppMemberLoginLogDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *AppMemberLoginLogDao) Columns() AppMemberLoginLogColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *AppMemberLoginLogDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *AppMemberLoginLogDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *AppMemberLoginLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// MemberUrlBindingDao is the data access object for the table hg_member_url_binding.
|
||||
type MemberUrlBindingDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns MemberUrlBindingColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// MemberUrlBindingColumns defines and stores column names for the table hg_member_url_binding.
|
||||
type MemberUrlBindingColumns struct {
|
||||
Id string // 主键
|
||||
TenantId string // 租户ID
|
||||
MemberId string // 会员ID
|
||||
UrlPoolId string // URL地址池ID
|
||||
DomainSnapshot string // 绑定时域名快照
|
||||
OriginSnapshot string // 绑定时Origin快照
|
||||
BoundAt string // 绑定时间
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
}
|
||||
|
||||
// memberUrlBindingColumns holds the columns for the table hg_member_url_binding.
|
||||
var memberUrlBindingColumns = MemberUrlBindingColumns{
|
||||
Id: "id",
|
||||
TenantId: "tenant_id",
|
||||
MemberId: "member_id",
|
||||
UrlPoolId: "url_pool_id",
|
||||
DomainSnapshot: "domain_snapshot",
|
||||
OriginSnapshot: "origin_snapshot",
|
||||
BoundAt: "bound_at",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewMemberUrlBindingDao creates and returns a new DAO object for table data access.
|
||||
func NewMemberUrlBindingDao(handlers ...gdb.ModelHandler) *MemberUrlBindingDao {
|
||||
return &MemberUrlBindingDao{
|
||||
group: "default",
|
||||
table: "hg_member_url_binding",
|
||||
columns: memberUrlBindingColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *MemberUrlBindingDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *MemberUrlBindingDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *MemberUrlBindingDao) Columns() MemberUrlBindingColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *MemberUrlBindingDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *MemberUrlBindingDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *MemberUrlBindingDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// UrlPoolDao is the data access object for the table hg_url_pool.
|
||||
type UrlPoolDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns UrlPoolColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// UrlPoolColumns defines and stores column names for the table hg_url_pool.
|
||||
type UrlPoolColumns struct {
|
||||
Id string // 主键
|
||||
TenantId string // 租户ID
|
||||
Name string // 地址名称
|
||||
Scheme string // 协议:http或https
|
||||
Domain string // 标准化域名,可含非默认端口
|
||||
DomainHash string // 标准化域名SHA256
|
||||
BaseUrl string // 注册入口基础地址
|
||||
Status string // 状态:1启用,2失效
|
||||
BindCount string // 累计成功绑定次数
|
||||
LastBoundAt string // 最后绑定时间
|
||||
Remark string // 备注
|
||||
CreatedBy string // 创建者ID
|
||||
UpdatedBy string // 更新者ID
|
||||
DeletedBy string // 删除者ID
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
DeletedAt string // 删除时间
|
||||
}
|
||||
|
||||
// urlPoolColumns holds the columns for the table hg_url_pool.
|
||||
var urlPoolColumns = UrlPoolColumns{
|
||||
Id: "id",
|
||||
TenantId: "tenant_id",
|
||||
Name: "name",
|
||||
Scheme: "scheme",
|
||||
Domain: "domain",
|
||||
DomainHash: "domain_hash",
|
||||
BaseUrl: "base_url",
|
||||
Status: "status",
|
||||
BindCount: "bind_count",
|
||||
LastBoundAt: "last_bound_at",
|
||||
Remark: "remark",
|
||||
CreatedBy: "created_by",
|
||||
UpdatedBy: "updated_by",
|
||||
DeletedBy: "deleted_by",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
DeletedAt: "deleted_at",
|
||||
}
|
||||
|
||||
// NewUrlPoolDao creates and returns a new DAO object for table data access.
|
||||
func NewUrlPoolDao(handlers ...gdb.ModelHandler) *UrlPoolDao {
|
||||
return &UrlPoolDao{
|
||||
group: "default",
|
||||
table: "hg_url_pool",
|
||||
columns: urlPoolColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *UrlPoolDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *UrlPoolDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *UrlPoolDao) Columns() UrlPoolColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *UrlPoolDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *UrlPoolDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *UrlPoolDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// memberUrlBindingDao is the data access object for the table hg_member_url_binding.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type memberUrlBindingDao struct {
|
||||
*internal.MemberUrlBindingDao
|
||||
}
|
||||
|
||||
var (
|
||||
// MemberUrlBinding is a globally accessible object for table hg_member_url_binding operations.
|
||||
MemberUrlBinding = memberUrlBindingDao{internal.NewMemberUrlBindingDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"hotgo/internal/dao/internal"
|
||||
)
|
||||
|
||||
// urlPoolDao is the data access object for the table hg_url_pool.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type urlPoolDao struct {
|
||||
*internal.UrlPoolDao
|
||||
}
|
||||
|
||||
var (
|
||||
// UrlPool is a globally accessible object for table hg_url_pool operations.
|
||||
UrlPool = urlPoolDao{internal.NewUrlPoolDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -55,10 +55,13 @@ func col(alias, column string) string { return alias + "." + column }
|
||||
|
||||
func (s *sSysAppMember) List(ctx context.Context, in *sysin.AppMemberListInp) (list []*sysin.AppMemberListModel, totalCount int, err error) {
|
||||
mc, pc, wc := dao.AppMember.Columns(), dao.AppMemberProfile.Columns(), dao.WalletAccount.Columns()
|
||||
bc, uc := dao.MemberUrlBinding.Columns(), dao.UrlPool.Columns()
|
||||
mod := s.memberModel(ctx).As("m").
|
||||
LeftJoin(dao.AppMemberProfile.Table()+" p", col("p", pc.MemberId)+"="+col("m", mc.Id)).
|
||||
LeftJoin(dao.WalletAccount.Table()+" w", col("w", wc.MemberId)+"="+col("m", mc.Id)+" AND "+col("w", wc.Currency)+"='CNY'").
|
||||
LeftJoin(dao.AppMember.Table()+" parent", col("parent", mc.Id)+"="+col("m", mc.ParentId)).
|
||||
LeftJoin(dao.MemberUrlBinding.Table()+" ub", col("ub", bc.MemberId)+"="+col("m", mc.Id)).
|
||||
LeftJoin(dao.UrlPool.Table()+" up", col("up", uc.Id)+"="+col("ub", bc.UrlPoolId)).
|
||||
Fields(
|
||||
col("m", mc.Id), col("m", mc.MemberNo), col("m", mc.Username), col("m", mc.Mobile),
|
||||
col("p", pc.RealNameMask), fmt.Sprintf("COALESCE(%s,0) available_amount", col("w", wc.AvailableAmount)),
|
||||
@@ -67,6 +70,9 @@ func (s *sSysAppMember) List(ctx context.Context, in *sysin.AppMemberListInp) (l
|
||||
col("m", mc.MemberStatus), col("m", mc.RiskStatus), col("m", mc.AutoDispatch), col("m", mc.IsTestAccount),
|
||||
fmt.Sprintf("CASE WHEN %s=1 THEN %d ELSE %d END member_category", col("m", mc.IsTestAccount), consts.AppMemberCategoryFake, consts.AppMemberCategoryReal),
|
||||
col("m", mc.LastLoginAt), col("m", mc.LastLoginIpMask), col("m", mc.LastLoginRegion), col("m", mc.CreatedAt),
|
||||
fmt.Sprintf("COALESCE(%s,0) url_pool_id", col("ub", bc.UrlPoolId)),
|
||||
fmt.Sprintf("COALESCE(%s,'') register_domain", col("ub", bc.DomainSnapshot)), col("ub", bc.BoundAt)+" domain_bound_at",
|
||||
fmt.Sprintf("CASE WHEN %s IS NULL THEN %d WHEN %s=%d THEN %d ELSE %d END domain_status", col("ub", bc.Id), consts.AppMemberDomainUnbound, col("up", uc.Status), consts.UrlPoolStatusEnabled, consts.AppMemberDomainBoundEnabled, consts.AppMemberDomainBoundInvalid),
|
||||
)
|
||||
if in.Username != "" {
|
||||
mod = mod.WhereLike(col("m", mc.Username), "%"+in.Username+"%")
|
||||
@@ -85,6 +91,16 @@ func (s *sSysAppMember) List(ctx context.Context, in *sysin.AppMemberListInp) (l
|
||||
} else if in.MemberCategory == consts.AppMemberCategoryFake {
|
||||
mod = mod.Where(col("m", mc.IsTestAccount), 1)
|
||||
}
|
||||
if in.RegisterDomain != "" {
|
||||
mod = mod.WhereLike(col("ub", bc.DomainSnapshot), "%"+strings.ToLower(strings.TrimSpace(in.RegisterDomain))+"%")
|
||||
}
|
||||
if in.DomainStatus == consts.AppMemberDomainBoundEnabled {
|
||||
mod = mod.WhereNotNull(col("ub", bc.Id)).Where(col("up", uc.Status), consts.UrlPoolStatusEnabled)
|
||||
} else if in.DomainStatus == consts.AppMemberDomainUnbound {
|
||||
mod = mod.WhereNull(col("ub", bc.Id))
|
||||
} else if in.DomainStatus == consts.AppMemberDomainBoundInvalid {
|
||||
mod = mod.WhereNotNull(col("ub", bc.Id)).Where(col("up", uc.Status), consts.UrlPoolStatusInvalid)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween(col("m", mc.CreatedAt), in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
@@ -97,9 +113,11 @@ func (s *sSysAppMember) List(ctx context.Context, in *sysin.AppMemberListInp) (l
|
||||
|
||||
func (s *sSysAppMember) View(ctx context.Context, in *sysin.AppMemberViewInp) (res *sysin.AppMemberViewModel, err error) {
|
||||
mc, pc, wc := dao.AppMember.Columns(), dao.AppMemberProfile.Columns(), dao.WalletAccount.Columns()
|
||||
bc, uc := dao.MemberUrlBinding.Columns(), dao.UrlPool.Columns()
|
||||
res = new(sysin.AppMemberViewModel)
|
||||
err = s.memberModel(ctx).As("m").LeftJoin(dao.AppMemberProfile.Table()+" p", col("p", pc.MemberId)+"="+col("m", mc.Id)).LeftJoin(dao.WalletAccount.Table()+" w", col("w", wc.MemberId)+"="+col("m", mc.Id)+" AND "+col("w", wc.Currency)+"='CNY'").LeftJoin(dao.AppMember.Table()+" parent", col("parent", mc.Id)+"="+col("m", mc.ParentId)).Fields(
|
||||
err = s.memberModel(ctx).As("m").LeftJoin(dao.AppMemberProfile.Table()+" p", col("p", pc.MemberId)+"="+col("m", mc.Id)).LeftJoin(dao.WalletAccount.Table()+" w", col("w", wc.MemberId)+"="+col("m", mc.Id)+" AND "+col("w", wc.Currency)+"='CNY'").LeftJoin(dao.AppMember.Table()+" parent", col("parent", mc.Id)+"="+col("m", mc.ParentId)).LeftJoin(dao.MemberUrlBinding.Table()+" ub", col("ub", bc.MemberId)+"="+col("m", mc.Id)).LeftJoin(dao.UrlPool.Table()+" up", col("up", uc.Id)+"="+col("ub", bc.UrlPoolId)).Fields(
|
||||
col("m", mc.Id), col("m", mc.MemberNo), col("m", mc.Username), col("m", mc.Mobile), col("p", pc.RealNameMask), col("p", pc.IdNoMask), col("p", pc.KycStatus), fmt.Sprintf("COALESCE(%s,0) available_amount", col("w", wc.AvailableAmount)), fmt.Sprintf("COALESCE(%s,0) frozen_amount", col("w", wc.FrozenAmount)), col("m", mc.ParentId), fmt.Sprintf("COALESCE(%s,'') parent_username", col("parent", mc.Username)), col("m", mc.InviteCode), col("m", mc.MemberStatus), col("m", mc.RiskStatus), col("m", mc.AutoDispatch), col("m", mc.IsTestAccount), fmt.Sprintf("CASE WHEN %s=1 THEN %d ELSE %d END member_category", col("m", mc.IsTestAccount), consts.AppMemberCategoryFake, consts.AppMemberCategoryReal), col("m", mc.LastLoginAt), col("m", mc.LastLoginIpMask), col("m", mc.LastLoginRegion), col("m", mc.CreatedAt), col("m", mc.LevelId), col("m", mc.Level), col("m", mc.CreditScore),
|
||||
fmt.Sprintf("COALESCE(%s,0) url_pool_id", col("ub", bc.UrlPoolId)), fmt.Sprintf("COALESCE(%s,'') register_domain", col("ub", bc.DomainSnapshot)), col("ub", bc.BoundAt)+" domain_bound_at", fmt.Sprintf("CASE WHEN %s IS NULL THEN %d WHEN %s=%d THEN %d ELSE %d END domain_status", col("ub", bc.Id), consts.AppMemberDomainUnbound, col("up", uc.Status), consts.UrlPoolStatusEnabled, consts.AppMemberDomainBoundEnabled, consts.AppMemberDomainBoundInvalid),
|
||||
).Where(col("m", mc.Id), in.Id).Scan(res)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取会员详情失败")
|
||||
@@ -115,6 +133,9 @@ func (s *sSysAppMember) View(ctx context.Context, in *sysin.AppMemberViewInp) (r
|
||||
}
|
||||
|
||||
func (s *sSysAppMember) Edit(ctx context.Context, in *sysin.AppMemberEditInp) error {
|
||||
if in.Id == 0 {
|
||||
return gerror.New("后台不支持创建会员,请由会员在C端完成注册")
|
||||
}
|
||||
isTestAccount := 2
|
||||
if in.MemberCategory == consts.AppMemberCategoryFake {
|
||||
isTestAccount = 1
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/library/location"
|
||||
"hotgo/internal/library/token"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/encrypt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type sSysAppMemberAuth struct{}
|
||||
|
||||
func NewSysAppMemberAuth() *sSysAppMemberAuth { return &sSysAppMemberAuth{} }
|
||||
|
||||
func init() { service.RegisterSysAppMemberAuth(NewSysAppMemberAuth()) }
|
||||
|
||||
func (s *sSysAppMemberAuth) memberModel(ctx context.Context) *gdb.Model {
|
||||
return handler.Model(dao.AppMember.Ctx(ctx), &handler.Option{FilterTenant: true})
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberAuth) Register(ctx context.Context, in *sysin.AppMemberRegisterInp) (res *sysin.AppMemberRegisterModel, err error) {
|
||||
mc, wc, lc := dao.AppMember.Columns(), dao.WalletAccount.Columns(), dao.AppMemberLevel.Columns()
|
||||
res = new(sysin.AppMemberRegisterModel)
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
count, e := s.memberModel(ctx).Where(mc.Username, in.Username).Count()
|
||||
if e != nil {
|
||||
return gerror.Wrap(e, "检查用户名失败")
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("用户名已存在")
|
||||
}
|
||||
count, e = s.memberModel(ctx).Where(mc.Mobile, in.Mobile).Count()
|
||||
if e != nil {
|
||||
return gerror.Wrap(e, "检查手机号失败")
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("手机号已存在")
|
||||
}
|
||||
|
||||
parentId, level, tree := uint64(0), 1, ""
|
||||
if in.InviteCode != "" {
|
||||
var parent *entity.AppMember
|
||||
if e = s.memberModel(ctx).Where(mc.InviteCode, in.InviteCode).Scan(&parent); e != nil {
|
||||
return gerror.Wrap(e, "查询邀请人失败")
|
||||
}
|
||||
if parent == nil {
|
||||
return gerror.New("邀请码不存在")
|
||||
}
|
||||
parentId = parent.Id
|
||||
level = parent.Level + 1
|
||||
tree = parent.Tree + "tr_" + fmt.Sprint(parent.Id) + " "
|
||||
}
|
||||
|
||||
levelId := uint64(0)
|
||||
var firstLevel *entity.AppMemberLevel
|
||||
if e = handler.Model(dao.AppMemberLevel.Ctx(ctx), &handler.Option{FilterTenant: true}).Where(lc.TenantId, 0).Where(lc.Status, consts.StatusEnabled).OrderAsc(lc.Sort).OrderAsc(lc.Id).Scan(&firstLevel); e != nil {
|
||||
return gerror.Wrap(e, "查询默认会员等级失败")
|
||||
}
|
||||
if firstLevel != nil {
|
||||
levelId = firstLevel.Id
|
||||
}
|
||||
hashed, e := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
|
||||
if e != nil {
|
||||
return gerror.Wrap(e, "生成密码摘要失败")
|
||||
}
|
||||
memberNo := "MB" + gtime.Now().Format("YmdHis") + grand.S(5)
|
||||
memberData := g.Map{
|
||||
mc.TenantId: 0,
|
||||
mc.MemberNo: memberNo, mc.Username: in.Username, mc.Mobile: in.Mobile,
|
||||
mc.PasswordHash: string(hashed), mc.LevelId: levelId, mc.ParentId: parentId,
|
||||
mc.Level: level, mc.Tree: tree, mc.InviteCode: strings.ToUpper(grand.S(8)),
|
||||
mc.MemberStatus: consts.AppMemberStatusNormal, mc.RiskStatus: consts.AppMemberRiskNormal,
|
||||
mc.AutoDispatch: consts.AppMemberDispatchManual, mc.CreditScore: 100, mc.IsTestAccount: 2,
|
||||
}
|
||||
result, e := dao.AppMember.Ctx(ctx).Fields(
|
||||
mc.TenantId, mc.MemberNo, mc.Username, mc.Mobile, mc.PasswordHash, mc.LevelId, mc.ParentId, mc.Level,
|
||||
mc.Tree, mc.InviteCode, mc.MemberStatus, mc.RiskStatus, mc.AutoDispatch, mc.CreditScore, mc.IsTestAccount,
|
||||
).Data(memberData).Insert()
|
||||
if e != nil {
|
||||
return gerror.Wrap(e, "创建会员失败")
|
||||
}
|
||||
id, e := result.LastInsertId()
|
||||
if e != nil {
|
||||
return gerror.Wrap(e, "读取会员ID失败")
|
||||
}
|
||||
res.MemberId = uint64(id)
|
||||
res.MemberNo = memberNo
|
||||
if _, e = dao.WalletAccount.Ctx(ctx).Fields(wc.TenantId, wc.MemberId, wc.Currency).Data(g.Map{wc.TenantId: 0, wc.MemberId: res.MemberId, wc.Currency: "CNY"}).Insert(); e != nil {
|
||||
return gerror.Wrap(e, "初始化会员钱包失败")
|
||||
}
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
origin, host := in.RegisterOrigin, in.RegisterHost
|
||||
if r != nil {
|
||||
if r.Header.Get("Origin") != "" {
|
||||
origin = r.Header.Get("Origin")
|
||||
}
|
||||
if host == "" {
|
||||
host = r.Host
|
||||
}
|
||||
}
|
||||
res.Bound, res.Domain, e = service.SysUrlPool().BindOnRegister(ctx, res.MemberId, origin, host)
|
||||
return e
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberAuth) AccountLogin(ctx context.Context, in *sysin.AppMemberAccountLoginInp) (res *sysin.AppMemberLoginModel, err error) {
|
||||
var member *entity.AppMember
|
||||
defer func() {
|
||||
service.SysAppMemberLoginLog().Push(ctx, &sysin.AppMemberLoginLogPushInp{Member: member, LoginType: consts.AppMemberLoginTypePassword, Account: in.Account, Err: err})
|
||||
}()
|
||||
mc := dao.AppMember.Columns()
|
||||
if err = s.memberModel(ctx).Where(fmt.Sprintf("(%s=? OR %s=?)", mc.Username, mc.Mobile), in.Account, in.Account).Scan(&member); err != nil {
|
||||
err = gerror.Wrap(err, "登录失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
if member == nil || bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte(in.Password)) != nil {
|
||||
err = gerror.New("账号或密码错误")
|
||||
return
|
||||
}
|
||||
return s.finishLogin(ctx, member)
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberAuth) MobileLogin(ctx context.Context, in *sysin.AppMemberMobileLoginInp) (res *sysin.AppMemberLoginModel, err error) {
|
||||
var member *entity.AppMember
|
||||
defer func() {
|
||||
service.SysAppMemberLoginLog().Push(ctx, &sysin.AppMemberLoginLogPushInp{Member: member, LoginType: consts.AppMemberLoginTypeSms, Account: in.Mobile, Err: err})
|
||||
}()
|
||||
mc := dao.AppMember.Columns()
|
||||
if err = s.memberModel(ctx).Where(mc.Mobile, in.Mobile).Scan(&member); err != nil {
|
||||
err = gerror.Wrap(err, "登录失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
if member == nil {
|
||||
err = gerror.New("会员不存在")
|
||||
return
|
||||
}
|
||||
if err = service.SysSmsLog().VerifyCode(ctx, &sysin.VerifyCodeInp{Event: consts.SmsTemplateLogin, Mobile: in.Mobile, Code: in.Code}); err != nil {
|
||||
err = gerror.New("验证码错误或已过期")
|
||||
return
|
||||
}
|
||||
return s.finishLogin(ctx, member)
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberAuth) finishLogin(ctx context.Context, member *entity.AppMember) (res *sysin.AppMemberLoginModel, err error) {
|
||||
if member.MemberStatus != consts.AppMemberStatusNormal {
|
||||
return nil, gerror.New("会员已禁用")
|
||||
}
|
||||
if member.RiskStatus == consts.AppMemberRiskBlocked {
|
||||
return nil, gerror.New("风控限制登录")
|
||||
}
|
||||
if err = s.updateLastLogin(ctx, member); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := gtime.Now()
|
||||
identity := &model.Identity{Id: int64(member.Id), Username: member.Username, App: consts.AppApi, LoginAt: now}
|
||||
loginToken, expires, err := token.Login(ctx, identity)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "登录失败,请稍后重试")
|
||||
}
|
||||
res = &sysin.AppMemberLoginModel{Id: member.Id, MemberNo: member.MemberNo, Username: member.Username, Mobile: encrypt.MaskMobile(member.Mobile), Token: loginToken, Expires: expires}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberAuth) updateLastLogin(ctx context.Context, member *entity.AppMember) error {
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
if r == nil {
|
||||
return gerror.New("登录失败,请稍后重试")
|
||||
}
|
||||
clientIP := location.GetClientIp(r)
|
||||
cipher, err := encrypt.EncryptField(ctx, clientIP)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "登录失败,请稍后重试")
|
||||
}
|
||||
region := ""
|
||||
if ipData, locationErr := location.GetLocation(ctx, clientIP); locationErr == nil && ipData != nil {
|
||||
region = formatLoginRegion(ipData)
|
||||
} else if locationErr != nil {
|
||||
g.Log().Warningf(ctx, "解析会员登录地区失败 memberId:%d ip:%s err:%+v", member.Id, maskIP(clientIP), locationErr)
|
||||
}
|
||||
mc := dao.AppMember.Columns()
|
||||
_, err = s.memberModel(ctx).Fields(mc.LastLoginAt, mc.LastLoginIpCipher, mc.LastLoginIpMask, mc.LastLoginRegion).WherePri(member.Id).Data(g.Map{
|
||||
mc.LastLoginAt: gtime.Now(), mc.LastLoginIpCipher: cipher, mc.LastLoginIpMask: maskIP(clientIP), mc.LastLoginRegion: region,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "登录失败,请稍后重试")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/library/location"
|
||||
"hotgo/internal/library/queue"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/encrypt"
|
||||
"hotgo/utility/excel"
|
||||
"hotgo/utility/useragent"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysAppMemberLoginLog struct{}
|
||||
|
||||
func NewSysAppMemberLoginLog() *sSysAppMemberLoginLog { return &sSysAppMemberLoginLog{} }
|
||||
|
||||
func init() { service.RegisterSysAppMemberLoginLog(NewSysAppMemberLoginLog()) }
|
||||
|
||||
func (s *sSysAppMemberLoginLog) Model(ctx context.Context) *gdb.Model {
|
||||
return handler.Model(dao.AppMemberLoginLog.Ctx(ctx), &handler.Option{FilterTenant: true})
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberLoginLog) List(ctx context.Context, in *sysin.AppMemberLoginLogListInp) (list []*sysin.AppMemberLoginLogListModel, totalCount int, err error) {
|
||||
lc, mc := dao.AppMemberLoginLog.Columns(), dao.AppMember.Columns()
|
||||
mod := s.Model(ctx).As("l").LeftJoin(dao.AppMember.Table()+" m", "m."+mc.Id+"=l."+lc.MemberId)
|
||||
if in.Keyword != "" {
|
||||
keyword := strings.TrimSpace(in.Keyword)
|
||||
like := "%" + keyword + "%"
|
||||
mod = mod.Where(fmt.Sprintf("(m.%s LIKE ? OR l.%s LIKE ? OR l.%s LIKE ? OR l.%s LIKE ?)", mc.MemberNo, lc.Username, lc.MobileMask, lc.MemberId), like, like, like, like)
|
||||
}
|
||||
if in.LoginType > 0 {
|
||||
mod = mod.Where("l."+lc.LoginType, in.LoginType)
|
||||
}
|
||||
if in.Status > 0 {
|
||||
mod = mod.Where("l."+lc.Status, in.Status)
|
||||
}
|
||||
if in.LoginDomain != "" {
|
||||
mod = mod.WhereLike("l."+lc.LoginDomain, "%"+strings.ToLower(strings.TrimSpace(in.LoginDomain))+"%")
|
||||
}
|
||||
if in.LoginIp != "" {
|
||||
mod = mod.WhereLike("l."+lc.LoginIpMask, "%"+strings.TrimSpace(in.LoginIp)+"%")
|
||||
}
|
||||
if len(in.LoginAt) == 2 {
|
||||
mod = mod.WhereBetween("l."+lc.LoginAt, in.LoginAt[0], in.LoginAt[1])
|
||||
}
|
||||
totalCount, err = mod.Clone().Count()
|
||||
if err != nil || totalCount == 0 {
|
||||
return
|
||||
}
|
||||
mod = mod.Fields(
|
||||
"l."+lc.Id, "l."+lc.ReqId, "l."+lc.MemberId,
|
||||
fmt.Sprintf("COALESCE(m.%s,'') member_no", mc.MemberNo),
|
||||
"l."+lc.Username, "l."+lc.MobileMask, "l."+lc.LoginType, "l."+lc.Status,
|
||||
"l."+lc.ErrMsg, "l."+lc.LoginAt, "l."+lc.LoginIpMask, "l."+lc.LoginRegion,
|
||||
"l."+lc.LoginDomain, "l."+lc.UserAgent,
|
||||
)
|
||||
err = mod.Page(in.Page, in.PerPage).OrderDesc("l." + lc.LoginAt).OrderDesc("l." + lc.Id).Scan(&list)
|
||||
if err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "查询会员登录日志失败")
|
||||
}
|
||||
for _, row := range list {
|
||||
row.Os = useragent.GetOs(row.UserAgent)
|
||||
row.Browser = useragent.GetBrowser(row.UserAgent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberLoginLog) View(ctx context.Context, in *sysin.AppMemberLoginLogViewInp) (res *sysin.AppMemberLoginLogViewModel, err error) {
|
||||
lc, mc := dao.AppMemberLoginLog.Columns(), dao.AppMember.Columns()
|
||||
res = new(sysin.AppMemberLoginLogViewModel)
|
||||
err = s.Model(ctx).As("l").LeftJoin(dao.AppMember.Table()+" m", "m."+mc.Id+"=l."+lc.MemberId).Fields(
|
||||
"l."+lc.Id, "l."+lc.ReqId, "l."+lc.MemberId,
|
||||
fmt.Sprintf("COALESCE(m.%s,'') member_no", mc.MemberNo),
|
||||
"l."+lc.Username, "l."+lc.MobileMask, "l."+lc.LoginType, "l."+lc.Status,
|
||||
"l."+lc.ErrMsg, "l."+lc.LoginAt, "l."+lc.LoginIpMask, "l."+lc.LoginRegion,
|
||||
"l."+lc.LoginDomain, "l."+lc.UserAgent,
|
||||
).Where("l."+lc.Id, in.Id).Scan(res)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询会员登录日志详情失败")
|
||||
}
|
||||
if res.Id == 0 {
|
||||
return nil, gerror.New("会员登录日志不存在")
|
||||
}
|
||||
res.Os = useragent.GetOs(res.UserAgent)
|
||||
res.Browser = useragent.GetBrowser(res.UserAgent)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberLoginLog) Export(ctx context.Context, in *sysin.AppMemberLoginLogListInp) error {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var exports []sysin.AppMemberLoginLogExportModel
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return err
|
||||
}
|
||||
tags, err := convert.GetEntityDescTags(sysin.AppMemberLoginLogExportModel{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileName := "导出会员登录日志-" + gctx.CtxId(ctx)
|
||||
sheetName := fmt.Sprintf("共%v行,共%v页,当前第%v页,本页%v行", totalCount, form.CalPageCount(totalCount, in.PerPage), in.Page, len(exports))
|
||||
return excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberLoginLog) Push(ctx context.Context, in *sysin.AppMemberLoginLogPushInp) {
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
if r == nil {
|
||||
g.Log().Warningf(ctx, "会员登录日志缺少HTTP请求上下文")
|
||||
return
|
||||
}
|
||||
model := entity.AppMemberLoginLog{
|
||||
ReqId: gctx.CtxId(ctx),
|
||||
LoginType: in.LoginType,
|
||||
Status: consts.StatusEnabled,
|
||||
LoginAt: gtime.Now(),
|
||||
UserAgent: r.UserAgent(),
|
||||
}
|
||||
if in.Member != nil {
|
||||
model.TenantId = in.Member.TenantId
|
||||
model.MemberId = in.Member.Id
|
||||
model.Username = in.Member.Username
|
||||
model.MobileMask = encrypt.MaskMobile(in.Member.Mobile)
|
||||
} else if in.LoginType == consts.AppMemberLoginTypeSms {
|
||||
model.MobileMask = encrypt.MaskMobile(in.Account)
|
||||
} else {
|
||||
model.Username = strings.TrimSpace(in.Account)
|
||||
}
|
||||
if in.Err != nil {
|
||||
model.Status = consts.StatusDisable
|
||||
model.ErrMsg = normalizeLoginError(in.Err)
|
||||
}
|
||||
clientIP := location.GetClientIp(r)
|
||||
model.LoginIpMask = maskIP(clientIP)
|
||||
if cipher, err := encrypt.EncryptField(ctx, clientIP); err == nil {
|
||||
model.LoginIpCipher = cipher
|
||||
} else {
|
||||
g.Log().Warningf(ctx, "加密会员登录IP失败 memberId:%d err:%+v", model.MemberId, err)
|
||||
}
|
||||
if ipData, err := location.GetLocation(ctx, clientIP); err == nil && ipData != nil {
|
||||
model.LoginRegion = formatLoginRegion(ipData)
|
||||
} else if err != nil {
|
||||
g.Log().Warningf(ctx, "解析会员登录地区失败 memberId:%d ip:%s err:%+v", model.MemberId, model.LoginIpMask, err)
|
||||
}
|
||||
origin := r.Header.Get("Origin")
|
||||
if domain, _, _ := resolveRequestDomain(origin, r.Host); domain != "" {
|
||||
model.LoginDomain = domain
|
||||
}
|
||||
if err := queue.Push(consts.QueueAppMemberLoginLogTopic, model); err != nil {
|
||||
g.Log().Warningf(ctx, "推送会员登录日志失败,改为同步写入 memberId:%d err:%+v", model.MemberId, err)
|
||||
if writeErr := s.RealWrite(ctx, model); writeErr != nil {
|
||||
g.Log().Warningf(ctx, "同步写入会员登录日志失败 memberId:%d err:%+v", model.MemberId, writeErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sSysAppMemberLoginLog) RealWrite(ctx context.Context, model entity.AppMemberLoginLog) error {
|
||||
_, err := dao.AppMemberLoginLog.Ctx(ctx).Data(model).OmitEmptyData().Insert()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "写入会员登录日志失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func maskIP(value string) string {
|
||||
ip := net.ParseIP(strings.TrimSpace(value))
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
return fmt.Sprintf("%d.%d.*.*", v4[0], v4[1])
|
||||
}
|
||||
parts := strings.Split(ip.String(), ":")
|
||||
if len(parts) > 4 {
|
||||
return strings.Join(parts[:4], ":") + ":****"
|
||||
}
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
func formatLoginRegion(data *location.IpLocationData) string {
|
||||
parts := []string{data.Country, data.Province, data.City, data.Area}
|
||||
seen := make(map[string]struct{})
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[part]; ok {
|
||||
continue
|
||||
}
|
||||
seen[part] = struct{}{}
|
||||
result = append(result, part)
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return strings.TrimSpace(data.Region)
|
||||
}
|
||||
return strings.Join(result, " ")
|
||||
}
|
||||
|
||||
func normalizeLoginError(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
message := err.Error()
|
||||
allowed := []string{"账号或密码错误", "验证码错误或已过期", "会员不存在", "会员已禁用", "风控限制登录", "登录失败,请稍后重试"}
|
||||
for _, item := range allowed {
|
||||
if strings.Contains(message, item) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return "系统异常"
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/library/hgorm/hook"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/encrypt"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type sSysUrlPool struct{}
|
||||
|
||||
func NewSysUrlPool() *sSysUrlPool { return &sSysUrlPool{} }
|
||||
|
||||
func init() { service.RegisterSysUrlPool(NewSysUrlPool()) }
|
||||
|
||||
func (s *sSysUrlPool) Model(ctx context.Context) *gdb.Model {
|
||||
return handler.Model(dao.UrlPool.Ctx(ctx), &handler.Option{FilterTenant: true})
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) bindingModel(ctx context.Context) *gdb.Model {
|
||||
return handler.Model(dao.MemberUrlBinding.Ctx(ctx), &handler.Option{FilterTenant: true})
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) List(ctx context.Context, in *sysin.UrlPoolListInp) (list []*sysin.UrlPoolListModel, totalCount int, err error) {
|
||||
pc, bc := dao.UrlPool.Columns(), dao.MemberUrlBinding.Columns()
|
||||
mod := s.Model(ctx).As("p")
|
||||
if in.Name != "" {
|
||||
mod = mod.WhereLike("p."+pc.Name, "%"+in.Name+"%")
|
||||
}
|
||||
if in.Domain != "" {
|
||||
mod = mod.WhereLike("p."+pc.Domain, "%"+strings.ToLower(strings.TrimSpace(in.Domain))+"%")
|
||||
}
|
||||
if in.Status > 0 {
|
||||
mod = mod.Where("p."+pc.Status, in.Status)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween("p."+pc.CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
totalCount, err = mod.Clone().Count()
|
||||
if err != nil || totalCount == 0 {
|
||||
return
|
||||
}
|
||||
mod = mod.Fields(
|
||||
"p."+pc.Id, "p."+pc.Name, "p."+pc.Scheme, "p."+pc.Domain, "p."+pc.BaseUrl,
|
||||
"p."+pc.Status, "p."+pc.BindCount, "p."+pc.LastBoundAt, "p."+pc.Remark,
|
||||
"p."+pc.CreatedAt, "p."+pc.UpdatedAt,
|
||||
fmt.Sprintf("(SELECT COUNT(*) FROM %s b WHERE b.%s=p.%s) current_bound_count", dao.MemberUrlBinding.Table(), bc.UrlPoolId, pc.Id),
|
||||
)
|
||||
err = mod.Page(in.Page, in.PerPage).OrderAsc("p." + pc.BindCount).OrderAsc("p." + pc.Id).Scan(&list)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) View(ctx context.Context, in *sysin.UrlPoolViewInp) (res *sysin.UrlPoolViewModel, err error) {
|
||||
pc, bc := dao.UrlPool.Columns(), dao.MemberUrlBinding.Columns()
|
||||
res = new(sysin.UrlPoolViewModel)
|
||||
err = s.Model(ctx).As("p").Fields(
|
||||
"p.*",
|
||||
fmt.Sprintf("(SELECT COUNT(*) FROM %s b WHERE b.%s=p.%s) current_bound_count", dao.MemberUrlBinding.Table(), bc.UrlPoolId, pc.Id),
|
||||
).Where("p."+pc.Id, in.Id).Scan(res)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询URL地址详情失败")
|
||||
}
|
||||
if res.Id == 0 {
|
||||
return nil, gerror.New("URL地址不存在")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) Edit(ctx context.Context, in *sysin.UrlPoolEditInp) (err error) {
|
||||
scheme, domain, baseURL, err := normalizeRegisterURL(in.BaseUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := domainHash(domain)
|
||||
pc := dao.UrlPool.Columns()
|
||||
var before *entity.UrlPool
|
||||
if in.Id > 0 {
|
||||
if err = s.Model(ctx).WherePri(in.Id).Scan(&before); err != nil {
|
||||
return gerror.Wrap(err, "读取URL地址失败")
|
||||
}
|
||||
if before == nil {
|
||||
return gerror.New("URL地址不存在")
|
||||
}
|
||||
if before.Domain != domain {
|
||||
count, e := s.bindingModel(ctx).Where(dao.MemberUrlBinding.Columns().UrlPoolId, in.Id).Count()
|
||||
if e != nil {
|
||||
return gerror.Wrap(e, "检查URL地址绑定失败")
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("该地址已绑定会员,不能修改域名")
|
||||
}
|
||||
}
|
||||
}
|
||||
count, err := s.Model(ctx).Where(pc.DomainHash, hash).WhereNot(pc.Id, in.Id).Count()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "检查URL地址重复失败")
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("该注册域名已存在")
|
||||
}
|
||||
data := g.Map{
|
||||
pc.Name: in.Name, pc.Scheme: scheme, pc.Domain: domain, pc.DomainHash: hash,
|
||||
pc.BaseUrl: baseURL, pc.Status: in.Status, pc.Remark: in.Remark,
|
||||
pc.UpdatedBy: contexts.GetUserId(ctx),
|
||||
}
|
||||
if in.Id > 0 {
|
||||
_, err = s.Model(ctx).Fields(pc.Name, pc.Scheme, pc.Domain, pc.DomainHash, pc.BaseUrl, pc.Status, pc.Remark, pc.UpdatedBy).WherePri(in.Id).Data(data).Hook(hook.SaveTenant).Update()
|
||||
} else {
|
||||
data[pc.CreatedBy] = contexts.GetUserId(ctx)
|
||||
result, e := dao.UrlPool.Ctx(ctx).Fields(pc.Name, pc.Scheme, pc.Domain, pc.DomainHash, pc.BaseUrl, pc.Status, pc.Remark, pc.CreatedBy, pc.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
||||
if e != nil {
|
||||
err = e
|
||||
} else {
|
||||
id, _ := result.LastInsertId()
|
||||
in.Id = uint64(id)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "保存URL地址失败")
|
||||
}
|
||||
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "url_pool", Action: "url_pool.edit", TargetType: "url_pool", TargetId: in.Id, Before: before, After: data})
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) Status(ctx context.Context, in *sysin.UrlPoolStatusInp) error {
|
||||
pc := dao.UrlPool.Columns()
|
||||
var before *entity.UrlPool
|
||||
if err := s.Model(ctx).WherePri(in.Id).Scan(&before); err != nil {
|
||||
return gerror.Wrap(err, "读取URL地址失败")
|
||||
}
|
||||
if before == nil {
|
||||
return gerror.New("URL地址不存在")
|
||||
}
|
||||
if before.Status == in.Status {
|
||||
return nil
|
||||
}
|
||||
if _, err := s.Model(ctx).Fields(pc.Status, pc.UpdatedBy).WherePri(in.Id).Data(g.Map{pc.Status: in.Status, pc.UpdatedBy: contexts.GetUserId(ctx)}).Hook(hook.SaveTenant).Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新URL地址状态失败")
|
||||
}
|
||||
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "url_pool", Action: "url_pool.status", TargetType: "url_pool", TargetId: in.Id, Before: g.Map{"status": before.Status}, After: g.Map{"status": in.Status}})
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) Import(ctx context.Context, reader io.Reader) (res *sysin.UrlPoolImportModel, err error) {
|
||||
book, err := excelize.OpenReader(reader)
|
||||
if err != nil {
|
||||
return nil, gerror.New("Excel文件无法解析,请确认文件格式正确")
|
||||
}
|
||||
defer book.Close()
|
||||
sheets := book.GetSheetList()
|
||||
if len(sheets) == 0 {
|
||||
return nil, gerror.New("Excel文件中没有工作表")
|
||||
}
|
||||
rows, err := book.GetRows(sheets[0])
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "读取Excel内容失败")
|
||||
}
|
||||
if len(rows) < 2 {
|
||||
return nil, gerror.New("Excel中没有可导入的数据")
|
||||
}
|
||||
nameColumn, urlColumn, remarkColumn := findUrlPoolImportColumns(rows[0])
|
||||
if nameColumn < 0 || urlColumn < 0 {
|
||||
return nil, gerror.New("Excel表头必须包含“地址名称”和“注册入口URL”")
|
||||
}
|
||||
|
||||
type importCandidate struct {
|
||||
domainHash string
|
||||
data g.Map
|
||||
}
|
||||
res = &sysin.UrlPoolImportModel{Issues: make([]*sysin.UrlPoolImportIssue, 0)}
|
||||
candidates := make([]importCandidate, 0, len(rows)-1)
|
||||
fileDomains := make(map[string]struct{})
|
||||
pc := dao.UrlPool.Columns()
|
||||
for index := 1; index < len(rows); index++ {
|
||||
row := rows[index]
|
||||
name := importCell(row, nameColumn)
|
||||
rawURL := importCell(row, urlColumn)
|
||||
remark := importCell(row, remarkColumn)
|
||||
if name == "" && rawURL == "" && remark == "" {
|
||||
continue
|
||||
}
|
||||
res.TotalRows++
|
||||
rowNumber := index + 1
|
||||
if name == "" {
|
||||
appendUrlPoolImportIssue(res, rowNumber, "地址名称不能为空")
|
||||
continue
|
||||
}
|
||||
if utf8.RuneCountInString(name) > 100 {
|
||||
appendUrlPoolImportIssue(res, rowNumber, "地址名称不能超过100个字符")
|
||||
continue
|
||||
}
|
||||
if utf8.RuneCountInString(remark) > 500 {
|
||||
appendUrlPoolImportIssue(res, rowNumber, "备注不能超过500个字符")
|
||||
continue
|
||||
}
|
||||
scheme, domain, baseURL, normalizeErr := normalizeRegisterURL(rawURL)
|
||||
if normalizeErr != nil {
|
||||
appendUrlPoolImportIssue(res, rowNumber, normalizeErr.Error())
|
||||
continue
|
||||
}
|
||||
if len(baseURL) > 512 {
|
||||
appendUrlPoolImportIssue(res, rowNumber, "注册入口URL不能超过512个字符")
|
||||
continue
|
||||
}
|
||||
hash := domainHash(domain)
|
||||
if _, exists := fileDomains[hash]; exists {
|
||||
res.DuplicateCount++
|
||||
continue
|
||||
}
|
||||
fileDomains[hash] = struct{}{}
|
||||
candidates = append(candidates, importCandidate{domainHash: hash, data: g.Map{
|
||||
pc.Name: name, pc.Scheme: scheme, pc.Domain: domain, pc.DomainHash: hash,
|
||||
pc.BaseUrl: baseURL, pc.Status: consts.UrlPoolStatusEnabled, pc.Remark: remark,
|
||||
pc.CreatedBy: contexts.GetUserId(ctx), pc.UpdatedBy: contexts.GetUserId(ctx),
|
||||
}})
|
||||
}
|
||||
if res.TotalRows > 5000 {
|
||||
return nil, gerror.New("单次最多导入5000条地址")
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
hashes := make([]string, 0, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
hashes = append(hashes, candidate.domainHash)
|
||||
}
|
||||
existingValues, err := s.Model(ctx).Fields(pc.DomainHash).WhereIn(pc.DomainHash, hashes).Array()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "检查重复注册域名失败")
|
||||
}
|
||||
existing := make(map[string]struct{}, len(existingValues))
|
||||
for _, value := range existingValues {
|
||||
existing[value.String()] = struct{}{}
|
||||
}
|
||||
insertRows := make(g.List, 0, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
if _, exists := existing[candidate.domainHash]; exists {
|
||||
res.DuplicateCount++
|
||||
continue
|
||||
}
|
||||
insertRows = append(insertRows, candidate.data)
|
||||
}
|
||||
if len(insertRows) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
_, insertErr := dao.UrlPool.Ctx(ctx).Fields(
|
||||
pc.Name, pc.Scheme, pc.Domain, pc.DomainHash, pc.BaseUrl, pc.Status, pc.Remark, pc.CreatedBy, pc.UpdatedBy,
|
||||
).Hook(hook.SaveTenant).Data(insertRows).Insert()
|
||||
return insertErr
|
||||
})
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "批量导入URL地址失败")
|
||||
}
|
||||
res.SuccessCount = len(insertRows)
|
||||
if auditErr := service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{
|
||||
Module: "url_pool", Action: "url_pool.import", TargetType: "url_pool", After: g.Map{
|
||||
"totalRows": res.TotalRows, "successCount": res.SuccessCount,
|
||||
"duplicateCount": res.DuplicateCount, "failedCount": res.FailedCount,
|
||||
},
|
||||
}); auditErr != nil {
|
||||
return nil, auditErr
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func findUrlPoolImportColumns(header []string) (nameColumn, urlColumn, remarkColumn int) {
|
||||
nameColumn, urlColumn, remarkColumn = -1, -1, -1
|
||||
for index, value := range header {
|
||||
value = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(value, "\ufeff")))
|
||||
switch value {
|
||||
case "地址名称", "名称", "name":
|
||||
nameColumn = index
|
||||
case "注册入口url", "注册入口", "入口url", "baseurl", "base_url":
|
||||
urlColumn = index
|
||||
case "备注", "remark":
|
||||
remarkColumn = index
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func importCell(row []string, column int) string {
|
||||
if column < 0 || column >= len(row) {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(row[column])
|
||||
}
|
||||
|
||||
func appendUrlPoolImportIssue(res *sysin.UrlPoolImportModel, row int, message string) {
|
||||
res.FailedCount++
|
||||
if len(res.Issues) < 100 {
|
||||
res.Issues = append(res.Issues, &sysin.UrlPoolImportIssue{Row: row, Message: message})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) MemberList(ctx context.Context, in *sysin.UrlPoolMemberListInp) (list []*sysin.UrlPoolMemberListModel, totalCount int, err error) {
|
||||
bc, mc := dao.MemberUrlBinding.Columns(), dao.AppMember.Columns()
|
||||
mod := s.bindingModel(ctx).As("b").LeftJoin(dao.AppMember.Table()+" m", "m."+mc.Id+"=b."+bc.MemberId).Where("b."+bc.UrlPoolId, in.UrlPoolId)
|
||||
if in.Keyword != "" {
|
||||
like := "%" + strings.TrimSpace(in.Keyword) + "%"
|
||||
mod = mod.Where(fmt.Sprintf("(m.%s LIKE ? OR m.%s LIKE ? OR m.%s LIKE ?)", mc.MemberNo, mc.Username, mc.Mobile), like, like, like)
|
||||
}
|
||||
if len(in.BoundAt) == 2 {
|
||||
mod = mod.WhereBetween("b."+bc.BoundAt, in.BoundAt[0], in.BoundAt[1])
|
||||
}
|
||||
totalCount, err = mod.Clone().Count()
|
||||
if err != nil || totalCount == 0 {
|
||||
return
|
||||
}
|
||||
mod = mod.Fields(
|
||||
"b."+bc.MemberId, "m."+mc.MemberNo, "m."+mc.Username, "m."+mc.Mobile, "m."+mc.MemberStatus, "b."+bc.BoundAt,
|
||||
)
|
||||
err = mod.Page(in.Page, in.PerPage).OrderDesc("b." + bc.Id).Scan(&list)
|
||||
for _, row := range list {
|
||||
row.Mobile = encrypt.MaskMobile(row.Mobile)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) Summary(ctx context.Context, in *sysin.UrlPoolSummaryInp) (res *sysin.UrlPoolSummaryModel, err error) {
|
||||
res = new(sysin.UrlPoolSummaryModel)
|
||||
pc, bc, mc := dao.UrlPool.Columns(), dao.MemberUrlBinding.Columns(), dao.AppMember.Columns()
|
||||
if res.TotalCount, err = s.Model(ctx).Count(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.EnabledCount, err = s.Model(ctx).Where(pc.Status, consts.UrlPoolStatusEnabled).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.InvalidCount, err = s.Model(ctx).Where(pc.Status, consts.UrlPoolStatusInvalid).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.BoundMemberCount, err = s.bindingModel(ctx).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.UnboundMemberCount, err = handler.Model(dao.AppMember.Ctx(ctx), &handler.Option{FilterTenant: true}).As("m").LeftJoin(dao.MemberUrlBinding.Table()+" b", "b."+bc.MemberId+"=m."+mc.Id).WhereNull("b." + bc.Id).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.InvalidMemberCount, err = s.bindingModel(ctx).As("b").InnerJoin(dao.UrlPool.Table()+" p", "p."+pc.Id+"=b."+bc.UrlPoolId).Where("p."+pc.Status, consts.UrlPoolStatusInvalid).Count()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysUrlPool) BindOnRegister(ctx context.Context, memberId uint64, origin, host string) (bound bool, domain string, err error) {
|
||||
domain, normalizedOrigin, err := resolveRequestDomain(origin, host)
|
||||
if err != nil || domain == "" {
|
||||
return false, "", nil
|
||||
}
|
||||
pc, bc := dao.UrlPool.Columns(), dao.MemberUrlBinding.Columns()
|
||||
var pool *entity.UrlPool
|
||||
err = s.Model(ctx).Where(pc.DomainHash, domainHash(domain)).Where(pc.Status, consts.UrlPoolStatusEnabled).Scan(&pool)
|
||||
if err != nil {
|
||||
return false, domain, gerror.Wrap(err, "查询注册域名失败")
|
||||
}
|
||||
if pool == nil {
|
||||
return false, domain, nil
|
||||
}
|
||||
var existing *entity.MemberUrlBinding
|
||||
if err = s.bindingModel(ctx).Where(bc.MemberId, memberId).Scan(&existing); err != nil {
|
||||
return false, domain, gerror.Wrap(err, "查询会员域名绑定失败")
|
||||
}
|
||||
if existing != nil {
|
||||
return existing.UrlPoolId == pool.Id, existing.DomainSnapshot, nil
|
||||
}
|
||||
now := gtime.Now()
|
||||
if _, err = dao.MemberUrlBinding.Ctx(ctx).Fields(bc.TenantId, bc.MemberId, bc.UrlPoolId, bc.DomainSnapshot, bc.OriginSnapshot, bc.BoundAt).Data(g.Map{bc.TenantId: pool.TenantId, bc.MemberId: memberId, bc.UrlPoolId: pool.Id, bc.DomainSnapshot: domain, bc.OriginSnapshot: normalizedOrigin, bc.BoundAt: now}).Insert(); err != nil {
|
||||
return false, domain, gerror.Wrap(err, "创建会员域名绑定失败")
|
||||
}
|
||||
if _, err = s.Model(ctx).Fields(pc.BindCount, pc.LastBoundAt).WherePri(pool.Id).Data(g.Map{pc.BindCount: gdb.Raw(pc.BindCount + "+1"), pc.LastBoundAt: now}).Update(); err != nil {
|
||||
return false, domain, gerror.Wrap(err, "更新域名绑定次数失败")
|
||||
}
|
||||
return true, domain, nil
|
||||
}
|
||||
|
||||
func normalizeRegisterURL(raw string) (scheme, domain, baseURL string, err error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return "", "", "", gerror.New("注册入口URL格式不正确")
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return "", "", "", gerror.New("注册入口只支持HTTP或HTTPS")
|
||||
}
|
||||
if u.User != nil || u.RawQuery != "" || u.Fragment != "" {
|
||||
return "", "", "", gerror.New("注册入口不能包含账号、查询参数或片段")
|
||||
}
|
||||
hostname := strings.TrimSuffix(strings.ToLower(strings.TrimSpace(u.Hostname())), ".")
|
||||
if hostname == "" {
|
||||
return "", "", "", gerror.New("注册域名不能为空")
|
||||
}
|
||||
port := u.Port()
|
||||
if (u.Scheme == "https" && port == "443") || (u.Scheme == "http" && port == "80") {
|
||||
port = ""
|
||||
}
|
||||
domain = hostname
|
||||
if port != "" {
|
||||
domain = net.JoinHostPort(hostname, port)
|
||||
}
|
||||
path := strings.TrimSpace(u.EscapedPath())
|
||||
if path == "" {
|
||||
path = "/register"
|
||||
}
|
||||
return strings.ToLower(u.Scheme), domain, strings.ToLower(u.Scheme) + "://" + domain + path, nil
|
||||
}
|
||||
|
||||
func resolveRequestDomain(origin, host string) (domain, normalizedOrigin string, err error) {
|
||||
if strings.TrimSpace(origin) != "" {
|
||||
u, e := url.Parse(strings.TrimSpace(origin))
|
||||
if e == nil && u.Scheme != "" && u.Host != "" && u.User == nil {
|
||||
_, domain, normalizedOrigin, e = normalizeRegisterURL(u.Scheme + "://" + u.Host + "/register")
|
||||
return domain, strings.TrimSuffix(normalizedOrigin, "/register"), e
|
||||
}
|
||||
}
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
return "", "", nil
|
||||
}
|
||||
_, domain, normalizedOrigin, err = normalizeRegisterURL("https://" + host + "/register")
|
||||
return domain, strings.TrimSuffix(normalizedOrigin, "/register"), err
|
||||
}
|
||||
|
||||
func domainHash(domain string) string {
|
||||
sum := sha256.Sum256([]byte(strings.ToLower(strings.TrimSpace(domain))))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package sys
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormalizeRegisterURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
raw string
|
||||
domain string
|
||||
baseURL string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "default register path", raw: "https://Example.COM", domain: "example.com", baseURL: "https://example.com/register"},
|
||||
{name: "keep custom port", raw: "http://example.com:8080/signup", domain: "example.com:8080", baseURL: "http://example.com:8080/signup"},
|
||||
{name: "drop default port", raw: "https://example.com:443/register", domain: "example.com", baseURL: "https://example.com/register"},
|
||||
{name: "reject query", raw: "https://example.com/register?code=1", wantErr: true},
|
||||
{name: "reject non http", raw: "ftp://example.com/register", wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, domain, baseURL, err := normalizeRegisterURL(tt.raw)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("normalizeRegisterURL(%q) expected error", tt.raw)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("normalizeRegisterURL(%q) returned error: %v", tt.raw, err)
|
||||
}
|
||||
if domain != tt.domain || baseURL != tt.baseURL {
|
||||
t.Fatalf("normalizeRegisterURL(%q) = domain %q, baseURL %q", tt.raw, domain, baseURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveRequestDomainPrefersOrigin(t *testing.T) {
|
||||
domain, origin, err := resolveRequestDomain("https://REGISTER.example.com", "ignored.example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if domain != "register.example.com" || origin != "https://register.example.com" {
|
||||
t.Fatalf("unexpected domain resolution: %q %q", domain, origin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindUrlPoolImportColumns(t *testing.T) {
|
||||
nameColumn, urlColumn, remarkColumn := findUrlPoolImportColumns([]string{"\ufeff地址名称", "注册入口URL", "备注"})
|
||||
if nameColumn != 0 || urlColumn != 1 || remarkColumn != 2 {
|
||||
t.Fatalf("unexpected import columns: name=%d url=%d remark=%d", nameColumn, urlColumn, remarkColumn)
|
||||
}
|
||||
|
||||
nameColumn, urlColumn, remarkColumn = findUrlPoolImportColumns([]string{"NAME", "base_url"})
|
||||
if nameColumn != 0 || urlColumn != 1 || remarkColumn != -1 {
|
||||
t.Fatalf("unexpected alias columns: name=%d url=%d remark=%d", nameColumn, urlColumn, remarkColumn)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// AppMemberLoginLog is the golang structure of table hg_app_member_login_log for DAO operations like Where/Data.
|
||||
type AppMemberLoginLog struct {
|
||||
g.Meta `orm:"table:hg_app_member_login_log, do:true"`
|
||||
Id any // 日志ID
|
||||
TenantId any // 租户ID
|
||||
ReqId any // 请求ID
|
||||
MemberId any // 会员ID,未识别会员时为0
|
||||
Username any // 用户名快照
|
||||
MobileMask any // 手机号脱敏值
|
||||
LoginType any // 登录方式:1账号密码,2手机验证码
|
||||
Status any // 登录状态:1成功,2失败
|
||||
ErrMsg any // 失败原因
|
||||
LoginAt *gtime.Time // 登录时间
|
||||
LoginIpCipher any // 登录IP密文
|
||||
LoginIpMask any // 登录IP脱敏值
|
||||
LoginRegion any // 登录地区
|
||||
LoginDomain any // 登录页面标准化域名
|
||||
UserAgent any // 浏览器UA
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// MemberUrlBinding is the golang structure of table hg_member_url_binding for DAO operations like Where/Data.
|
||||
type MemberUrlBinding struct {
|
||||
g.Meta `orm:"table:hg_member_url_binding, do:true"`
|
||||
Id any // 主键
|
||||
TenantId any // 租户ID
|
||||
MemberId any // 会员ID
|
||||
UrlPoolId any // URL地址池ID
|
||||
DomainSnapshot any // 绑定时域名快照
|
||||
OriginSnapshot any // 绑定时Origin快照
|
||||
BoundAt *gtime.Time // 绑定时间
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// UrlPool is the golang structure of table hg_url_pool for DAO operations like Where/Data.
|
||||
type UrlPool struct {
|
||||
g.Meta `orm:"table:hg_url_pool, do:true"`
|
||||
Id any // 主键
|
||||
TenantId any // 租户ID
|
||||
Name any // 地址名称
|
||||
Scheme any // 协议:http或https
|
||||
Domain any // 标准化域名,可含非默认端口
|
||||
DomainHash any // 标准化域名SHA256
|
||||
BaseUrl any // 注册入口基础地址
|
||||
Status any // 状态:1启用,2失效
|
||||
BindCount any // 累计成功绑定次数
|
||||
LastBoundAt *gtime.Time // 最后绑定时间
|
||||
Remark any // 备注
|
||||
CreatedBy any // 创建者ID
|
||||
UpdatedBy any // 更新者ID
|
||||
DeletedBy any // 删除者ID
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
DeletedAt *gtime.Time // 删除时间
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// AppMemberLoginLog is the golang structure for table app_member_login_log.
|
||||
type AppMemberLoginLog struct {
|
||||
Id uint64 `json:"id" orm:"id" description:"日志ID"`
|
||||
TenantId uint64 `json:"tenantId" orm:"tenant_id" description:"租户ID"`
|
||||
ReqId string `json:"reqId" orm:"req_id" description:"请求ID"`
|
||||
MemberId uint64 `json:"memberId" orm:"member_id" description:"会员ID,未识别会员时为0"`
|
||||
Username string `json:"username" orm:"username" description:"用户名快照"`
|
||||
MobileMask string `json:"mobileMask" orm:"mobile_mask" description:"手机号脱敏值"`
|
||||
LoginType int `json:"loginType" orm:"login_type" description:"登录方式:1账号密码,2手机验证码"`
|
||||
Status int `json:"status" orm:"status" description:"登录状态:1成功,2失败"`
|
||||
ErrMsg string `json:"errMsg" orm:"err_msg" description:"失败原因"`
|
||||
LoginAt *gtime.Time `json:"loginAt" orm:"login_at" description:"登录时间"`
|
||||
LoginIpCipher string `json:"loginIpCipher" orm:"login_ip_cipher" description:"登录IP密文"`
|
||||
LoginIpMask string `json:"loginIpMask" orm:"login_ip_mask" description:"登录IP脱敏值"`
|
||||
LoginRegion string `json:"loginRegion" orm:"login_region" description:"登录地区"`
|
||||
LoginDomain string `json:"loginDomain" orm:"login_domain" description:"登录页面标准化域名"`
|
||||
UserAgent string `json:"userAgent" orm:"user_agent" description:"浏览器UA"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// MemberUrlBinding is the golang structure for table member_url_binding.
|
||||
type MemberUrlBinding struct {
|
||||
Id uint64 `json:"id" orm:"id" description:"主键"`
|
||||
TenantId uint64 `json:"tenantId" orm:"tenant_id" description:"租户ID"`
|
||||
MemberId uint64 `json:"memberId" orm:"member_id" description:"会员ID"`
|
||||
UrlPoolId uint64 `json:"urlPoolId" orm:"url_pool_id" description:"URL地址池ID"`
|
||||
DomainSnapshot string `json:"domainSnapshot" orm:"domain_snapshot" description:"绑定时域名快照"`
|
||||
OriginSnapshot string `json:"originSnapshot" orm:"origin_snapshot" description:"绑定时Origin快照"`
|
||||
BoundAt *gtime.Time `json:"boundAt" orm:"bound_at" description:"绑定时间"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// UrlPool is the golang structure for table url_pool.
|
||||
type UrlPool struct {
|
||||
Id uint64 `json:"id" orm:"id" description:"主键"`
|
||||
TenantId uint64 `json:"tenantId" orm:"tenant_id" description:"租户ID"`
|
||||
Name string `json:"name" orm:"name" description:"地址名称"`
|
||||
Scheme string `json:"scheme" orm:"scheme" description:"协议:http或https"`
|
||||
Domain string `json:"domain" orm:"domain" description:"标准化域名,可含非默认端口"`
|
||||
DomainHash string `json:"domainHash" orm:"domain_hash" description:"标准化域名SHA256"`
|
||||
BaseUrl string `json:"baseUrl" orm:"base_url" description:"注册入口基础地址"`
|
||||
Status int `json:"status" orm:"status" description:"状态:1启用,2失效"`
|
||||
BindCount uint64 `json:"bindCount" orm:"bind_count" description:"累计成功绑定次数"`
|
||||
LastBoundAt *gtime.Time `json:"lastBoundAt" orm:"last_bound_at" description:"最后绑定时间"`
|
||||
Remark string `json:"remark" orm:"remark" description:"备注"`
|
||||
CreatedBy uint64 `json:"createdBy" orm:"created_by" description:"创建者ID"`
|
||||
UpdatedBy uint64 `json:"updatedBy" orm:"updated_by" description:"更新者ID"`
|
||||
DeletedBy uint64 `json:"deletedBy" orm:"deleted_by" description:"删除者ID"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"`
|
||||
DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"删除时间"`
|
||||
}
|
||||
@@ -21,6 +21,8 @@ type AppMemberListInp struct {
|
||||
MemberStatus int `json:"memberStatus" dc:"会员状态"`
|
||||
AutoDispatch int `json:"autoDispatch" dc:"派单模式"`
|
||||
MemberCategory int `json:"memberCategory" dc:"会员类别"`
|
||||
RegisterDomain string `json:"registerDomain" dc:"注册域名(模糊查询)"`
|
||||
DomainStatus int `json:"domainStatus" dc:"域名绑定状态"`
|
||||
CreatedAt []*gtime.Time `json:"createdAt" dc:"注册时间"`
|
||||
}
|
||||
|
||||
@@ -47,6 +49,10 @@ type AppMemberListModel struct {
|
||||
LastLoginRegion string `json:"lastLoginRegion"`
|
||||
TodayTradeCount int `json:"todayTradeCount"`
|
||||
CreatedAt *gtime.Time `json:"createdAt"`
|
||||
UrlPoolId uint64 `json:"urlPoolId"`
|
||||
RegisterDomain string `json:"registerDomain"`
|
||||
DomainStatus int `json:"domainStatus"`
|
||||
DomainBoundAt *gtime.Time `json:"domainBoundAt"`
|
||||
}
|
||||
|
||||
type AppMemberViewInp struct {
|
||||
@@ -85,11 +91,8 @@ type AppMemberEditInp struct {
|
||||
}
|
||||
|
||||
func (in *AppMemberEditInp) Filter(ctx context.Context) error {
|
||||
if in.Id == 0 && strings.TrimSpace(in.Mobile) == "" {
|
||||
return gerror.New("新增会员时手机号不能为空")
|
||||
}
|
||||
if in.Id == 0 && len(in.Password) < 8 {
|
||||
return gerror.New("新增会员密码不能少于8位")
|
||||
if in.Id == 0 {
|
||||
return gerror.New("后台不支持创建会员,请由会员在C端完成注册")
|
||||
}
|
||||
if in.Password != "" && (len(in.Password) < 8 || !containsLetterAndNumber(in.Password)) {
|
||||
return gerror.New("密码至少8位且必须同时包含字母和数字")
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package sysin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type AppMemberRegisterInp struct {
|
||||
Username string `json:"username" v:"required|length:3,64#用户名不能为空|用户名长度为3到64个字符"`
|
||||
Mobile string `json:"mobile" v:"required|phone-loose#手机号不能为空|手机号格式不正确"`
|
||||
Password string `json:"password" v:"required|length:8,64#密码不能为空|密码长度为8到64个字符"`
|
||||
InviteCode string `json:"inviteCode" v:"max-length:32#邀请码不能超过32个字符"`
|
||||
RegisterOrigin string `json:"registerOrigin" v:"max-length:512#注册来源不能超过512个字符"`
|
||||
RegisterHost string `json:"registerHost" v:"max-length:255#注册域名不能超过255个字符"`
|
||||
}
|
||||
|
||||
func (in *AppMemberRegisterInp) Filter(ctx context.Context) error {
|
||||
in.Username = strings.TrimSpace(in.Username)
|
||||
in.Mobile = strings.TrimSpace(in.Mobile)
|
||||
in.InviteCode = strings.TrimSpace(in.InviteCode)
|
||||
if !containsLetterAndNumber(in.Password) {
|
||||
return gerror.New("密码至少8位且必须同时包含字母和数字")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppMemberRegisterModel struct {
|
||||
MemberId uint64 `json:"memberId"`
|
||||
MemberNo string `json:"memberNo"`
|
||||
Bound bool `json:"bound"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
type AppMemberAccountLoginInp struct {
|
||||
Account string `json:"account" v:"required|max-length:64#登录账号不能为空|登录账号不能超过64个字符"`
|
||||
Password string `json:"password" v:"required|max-length:64#登录密码不能为空|登录密码不能超过64个字符"`
|
||||
LoginHost string `json:"loginHost" v:"max-length:255#登录域名不能超过255个字符"`
|
||||
}
|
||||
|
||||
func (in *AppMemberAccountLoginInp) Filter(ctx context.Context) error {
|
||||
in.Account = strings.TrimSpace(in.Account)
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppMemberMobileLoginInp struct {
|
||||
Mobile string `json:"mobile" v:"required|phone-loose#手机号不能为空|手机号格式不正确"`
|
||||
Code string `json:"code" v:"required|length:4,8#验证码不能为空|验证码格式不正确"`
|
||||
LoginHost string `json:"loginHost" v:"max-length:255#登录域名不能超过255个字符"`
|
||||
}
|
||||
|
||||
func (in *AppMemberMobileLoginInp) Filter(ctx context.Context) error {
|
||||
in.Mobile = strings.TrimSpace(in.Mobile)
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppMemberLoginModel struct {
|
||||
Id uint64 `json:"id"`
|
||||
MemberNo string `json:"memberNo"`
|
||||
Username string `json:"username"`
|
||||
Mobile string `json:"mobile"`
|
||||
Token string `json:"token"`
|
||||
Expires int64 `json:"expires"`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package sysin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/form"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type AppMemberLoginLogListInp struct {
|
||||
form.PageReq
|
||||
Keyword string `json:"keyword" dc:"会员编号、用户名或手机号"`
|
||||
LoginType int `json:"loginType" dc:"登录方式"`
|
||||
Status int `json:"status" dc:"登录状态"`
|
||||
LoginDomain string `json:"loginDomain" dc:"登录域名"`
|
||||
LoginIp string `json:"loginIp" dc:"登录IP脱敏值"`
|
||||
LoginAt []*gtime.Time `json:"loginAt" dc:"登录时间"`
|
||||
}
|
||||
|
||||
func (in *AppMemberLoginLogListInp) Filter(ctx context.Context) error {
|
||||
if in.LoginType > 0 && !dict.HasOptionKey(consts.AppMemberLoginTypeOptions, in.LoginType) {
|
||||
return gerror.New("登录方式不合法")
|
||||
}
|
||||
if in.Status > 0 && in.Status != consts.StatusEnabled && in.Status != consts.StatusDisable {
|
||||
return gerror.New("登录状态不合法")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppMemberLoginLogListModel struct {
|
||||
Id uint64 `json:"id"`
|
||||
ReqId string `json:"reqId"`
|
||||
MemberId uint64 `json:"memberId"`
|
||||
MemberNo string `json:"memberNo"`
|
||||
Username string `json:"username"`
|
||||
MobileMask string `json:"mobileMask"`
|
||||
LoginType int `json:"loginType"`
|
||||
Status int `json:"status"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
LoginAt *gtime.Time `json:"loginAt"`
|
||||
LoginIpMask string `json:"loginIpMask"`
|
||||
LoginRegion string `json:"loginRegion"`
|
||||
LoginDomain string `json:"loginDomain"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
Os string `json:"os"`
|
||||
Browser string `json:"browser"`
|
||||
}
|
||||
|
||||
type AppMemberLoginLogViewInp struct {
|
||||
Id uint64 `json:"id" v:"required|min:1#日志ID不能为空|日志ID不能为空"`
|
||||
}
|
||||
|
||||
func (in *AppMemberLoginLogViewInp) Filter(ctx context.Context) error { return nil }
|
||||
|
||||
type AppMemberLoginLogViewModel struct {
|
||||
AppMemberLoginLogListModel
|
||||
}
|
||||
|
||||
type AppMemberLoginLogExportModel struct {
|
||||
Id uint64 `json:"id" description:"日志ID"`
|
||||
MemberNo string `json:"memberNo" description:"会员编号"`
|
||||
Username string `json:"username" description:"用户名"`
|
||||
MobileMask string `json:"mobileMask" description:"手机号"`
|
||||
LoginType int `json:"loginType" description:"登录方式"`
|
||||
Status int `json:"status" description:"登录状态"`
|
||||
LoginAt *gtime.Time `json:"loginAt" description:"登录时间"`
|
||||
LoginIpMask string `json:"loginIpMask" description:"登录IP"`
|
||||
LoginRegion string `json:"loginRegion" description:"登录地区"`
|
||||
LoginDomain string `json:"loginDomain" description:"登录域名"`
|
||||
Os string `json:"os" description:"操作系统"`
|
||||
Browser string `json:"browser" description:"浏览器"`
|
||||
ErrMsg string `json:"errMsg" description:"失败原因"`
|
||||
}
|
||||
|
||||
type AppMemberLoginLogPushInp struct {
|
||||
Member *entity.AppMember
|
||||
LoginType int
|
||||
Account string
|
||||
Err error
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package sysin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model/input/form"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type UrlPoolListInp struct {
|
||||
form.PageReq
|
||||
Name string `json:"name" dc:"地址名称"`
|
||||
Domain string `json:"domain" dc:"域名关键词"`
|
||||
Status int `json:"status" dc:"状态"`
|
||||
CreatedAt []*gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
}
|
||||
|
||||
func (in *UrlPoolListInp) Filter(ctx context.Context) error {
|
||||
if in.Status > 0 && !dict.HasOptionKey(consts.UrlPoolStatusOptions, in.Status) {
|
||||
return gerror.New("URL地址状态不合法")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UrlPoolListModel struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Scheme string `json:"scheme"`
|
||||
Domain string `json:"domain"`
|
||||
BaseUrl string `json:"baseUrl"`
|
||||
Status int `json:"status"`
|
||||
BindCount uint64 `json:"bindCount"`
|
||||
CurrentBoundCount uint64 `json:"currentBoundCount"`
|
||||
LastBoundAt *gtime.Time `json:"lastBoundAt"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt *gtime.Time `json:"createdAt"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type UrlPoolViewInp struct {
|
||||
Id uint64 `json:"id" v:"required|min:1#地址ID不能为空|地址ID不能为空"`
|
||||
}
|
||||
|
||||
func (in *UrlPoolViewInp) Filter(ctx context.Context) error { return nil }
|
||||
|
||||
type UrlPoolViewModel struct {
|
||||
UrlPoolListModel
|
||||
CreatedBy uint64 `json:"createdBy"`
|
||||
UpdatedBy uint64 `json:"updatedBy"`
|
||||
}
|
||||
|
||||
type UrlPoolEditInp struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" v:"required|max-length:100#地址名称不能为空|地址名称不能超过100个字符"`
|
||||
BaseUrl string `json:"baseUrl" v:"required|max-length:512#注册入口不能为空|注册入口不能超过512个字符"`
|
||||
Status int `json:"status" v:"required#状态不能为空"`
|
||||
Remark string `json:"remark" v:"max-length:500#备注不能超过500个字符"`
|
||||
}
|
||||
|
||||
func (in *UrlPoolEditInp) Filter(ctx context.Context) error {
|
||||
in.Name = strings.TrimSpace(in.Name)
|
||||
in.BaseUrl = strings.TrimSpace(in.BaseUrl)
|
||||
if !dict.HasOptionKey(consts.UrlPoolStatusOptions, in.Status) {
|
||||
return gerror.New("URL地址状态不合法")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UrlPoolStatusInp struct {
|
||||
Id uint64 `json:"id" v:"required|min:1#地址ID不能为空|地址ID不能为空"`
|
||||
Status int `json:"status" v:"required#状态不能为空"`
|
||||
}
|
||||
|
||||
func (in *UrlPoolStatusInp) Filter(ctx context.Context) error {
|
||||
if !dict.HasOptionKey(consts.UrlPoolStatusOptions, in.Status) {
|
||||
return gerror.New("URL地址状态不合法")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UrlPoolImportIssue struct {
|
||||
Row int `json:"row" dc:"Excel行号"`
|
||||
Message string `json:"message" dc:"失败原因"`
|
||||
}
|
||||
|
||||
type UrlPoolImportModel struct {
|
||||
TotalRows int `json:"totalRows" dc:"有效数据行数"`
|
||||
SuccessCount int `json:"successCount" dc:"成功导入数量"`
|
||||
DuplicateCount int `json:"duplicateCount" dc:"重复跳过数量"`
|
||||
FailedCount int `json:"failedCount" dc:"校验失败数量"`
|
||||
Issues []*UrlPoolImportIssue `json:"issues" dc:"校验失败明细,最多返回100条"`
|
||||
}
|
||||
|
||||
type UrlPoolMemberListInp struct {
|
||||
form.PageReq
|
||||
UrlPoolId uint64 `json:"urlPoolId" v:"required|min:1#地址ID不能为空|地址ID不能为空"`
|
||||
Keyword string `json:"keyword" dc:"会员编号、用户名或手机号"`
|
||||
BoundAt []*gtime.Time `json:"boundAt" dc:"绑定时间"`
|
||||
}
|
||||
|
||||
func (in *UrlPoolMemberListInp) Filter(ctx context.Context) error { return nil }
|
||||
|
||||
type UrlPoolMemberListModel struct {
|
||||
MemberId uint64 `json:"memberId"`
|
||||
MemberNo string `json:"memberNo"`
|
||||
Username string `json:"username"`
|
||||
Mobile string `json:"mobile"`
|
||||
MemberStatus int `json:"memberStatus"`
|
||||
BoundAt *gtime.Time `json:"boundAt"`
|
||||
}
|
||||
|
||||
type UrlPoolSummaryInp struct{}
|
||||
|
||||
func (in *UrlPoolSummaryInp) Filter(ctx context.Context) error { return nil }
|
||||
|
||||
type UrlPoolSummaryModel struct {
|
||||
TotalCount int `json:"totalCount"`
|
||||
EnabledCount int `json:"enabledCount"`
|
||||
InvalidCount int `json:"invalidCount"`
|
||||
BoundMemberCount int `json:"boundMemberCount"`
|
||||
UnboundMemberCount int `json:"unboundMemberCount"`
|
||||
InvalidMemberCount int `json:"invalidMemberCount"`
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package queues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/queue"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
func init() { queue.RegisterConsumer(AppMemberLoginLog) }
|
||||
|
||||
var AppMemberLoginLog = &qAppMemberLoginLog{}
|
||||
|
||||
type qAppMemberLoginLog struct{}
|
||||
|
||||
func (q *qAppMemberLoginLog) GetTopic() string { return consts.QueueAppMemberLoginLogTopic }
|
||||
|
||||
func (q *qAppMemberLoginLog) Handle(ctx context.Context, mqMsg queue.MqMsg) error {
|
||||
var data entity.AppMemberLoginLog
|
||||
if err := json.Unmarshal(mqMsg.Body, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
return service.SysAppMemberLoginLog().RealWrite(ctx, data)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package genrouter
|
||||
|
||||
import "hotgo/internal/controller/admin/sys"
|
||||
|
||||
func init() { LoginRequiredRouter = append(LoginRequiredRouter, sys.AppMemberLoginLog) }
|
||||
@@ -0,0 +1,5 @@
|
||||
package genrouter
|
||||
|
||||
import "hotgo/internal/controller/admin/sys"
|
||||
|
||||
func init() { LoginRequiredRouter = append(LoginRequiredRouter, sys.UrlPool) }
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/utility/tree"
|
||||
"io"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
@@ -118,6 +119,28 @@ type (
|
||||
NoticeEdit(ctx context.Context, in *sysin.MemberNoticeEditInp) error
|
||||
NoticeRevoke(ctx context.Context, in *sysin.MemberNoticeRevokeInp) error
|
||||
}
|
||||
ISysUrlPool interface {
|
||||
List(ctx context.Context, in *sysin.UrlPoolListInp) (list []*sysin.UrlPoolListModel, totalCount int, err error)
|
||||
View(ctx context.Context, in *sysin.UrlPoolViewInp) (res *sysin.UrlPoolViewModel, err error)
|
||||
Edit(ctx context.Context, in *sysin.UrlPoolEditInp) error
|
||||
Status(ctx context.Context, in *sysin.UrlPoolStatusInp) error
|
||||
Import(ctx context.Context, reader io.Reader) (res *sysin.UrlPoolImportModel, err error)
|
||||
MemberList(ctx context.Context, in *sysin.UrlPoolMemberListInp) (list []*sysin.UrlPoolMemberListModel, totalCount int, err error)
|
||||
Summary(ctx context.Context, in *sysin.UrlPoolSummaryInp) (res *sysin.UrlPoolSummaryModel, err error)
|
||||
BindOnRegister(ctx context.Context, memberId uint64, origin, host string) (bound bool, domain string, err error)
|
||||
}
|
||||
ISysAppMemberLoginLog interface {
|
||||
List(ctx context.Context, in *sysin.AppMemberLoginLogListInp) (list []*sysin.AppMemberLoginLogListModel, totalCount int, err error)
|
||||
View(ctx context.Context, in *sysin.AppMemberLoginLogViewInp) (res *sysin.AppMemberLoginLogViewModel, err error)
|
||||
Export(ctx context.Context, in *sysin.AppMemberLoginLogListInp) error
|
||||
Push(ctx context.Context, in *sysin.AppMemberLoginLogPushInp)
|
||||
RealWrite(ctx context.Context, model entity.AppMemberLoginLog) error
|
||||
}
|
||||
ISysAppMemberAuth interface {
|
||||
Register(ctx context.Context, in *sysin.AppMemberRegisterInp) (res *sysin.AppMemberRegisterModel, err error)
|
||||
AccountLogin(ctx context.Context, in *sysin.AppMemberAccountLoginInp) (res *sysin.AppMemberLoginModel, err error)
|
||||
MobileLogin(ctx context.Context, in *sysin.AppMemberMobileLoginInp) (res *sysin.AppMemberLoginModel, err error)
|
||||
}
|
||||
ISysPortal interface {
|
||||
SiteConfigCurrent(ctx context.Context, in *sysin.SiteConfigCurrentInp) (res *sysin.SiteConfigCurrentModel, err error)
|
||||
SiteConfigHistory(ctx context.Context, in *sysin.SiteConfigHistoryInp) (list []*sysin.SiteConfigHistoryModel, totalCount int, err error)
|
||||
@@ -547,41 +570,44 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
localSysMemberLevel ISysMemberLevel
|
||||
localSysTaskHall ISysTaskHall
|
||||
localSysTradeOrder ISysTradeOrder
|
||||
localSysRechargeOrder ISysRechargeOrder
|
||||
localSysWithdrawalOrder ISysWithdrawalOrder
|
||||
localSysCustomerService ISysCustomerService
|
||||
localSysProduct ISysProduct
|
||||
localSysTask ISysTask
|
||||
localSysTaskSubmission ISysTaskSubmission
|
||||
localSysAppMember ISysAppMember
|
||||
localSysPortal ISysPortal
|
||||
localSysDashboard ISysDashboard
|
||||
localSysBizAudit ISysBizAudit
|
||||
localSysApproval ISysApproval
|
||||
localSysAddons ISysAddons
|
||||
localSysAddonsConfig ISysAddonsConfig
|
||||
localSysAttachment ISysAttachment
|
||||
localSysBlacklist ISysBlacklist
|
||||
localSysConfig ISysConfig
|
||||
localSysCron ISysCron
|
||||
localSysCronGroup ISysCronGroup
|
||||
localSysCurdDemo ISysCurdDemo
|
||||
localSysDictData ISysDictData
|
||||
localSysDictType ISysDictType
|
||||
localSysEmsLog ISysEmsLog
|
||||
localSysGenCodes ISysGenCodes
|
||||
localSysLog ISysLog
|
||||
localSysLoginLog ISysLoginLog
|
||||
localSysNormalTreeDemo ISysNormalTreeDemo
|
||||
localSysOptionTreeDemo ISysOptionTreeDemo
|
||||
localSysProvinces ISysProvinces
|
||||
localSysServeLicense ISysServeLicense
|
||||
localSysServeLog ISysServeLog
|
||||
localSysSmsLog ISysSmsLog
|
||||
localSysTestCategory ISysTestCategory
|
||||
localSysMemberLevel ISysMemberLevel
|
||||
localSysTaskHall ISysTaskHall
|
||||
localSysTradeOrder ISysTradeOrder
|
||||
localSysRechargeOrder ISysRechargeOrder
|
||||
localSysWithdrawalOrder ISysWithdrawalOrder
|
||||
localSysCustomerService ISysCustomerService
|
||||
localSysProduct ISysProduct
|
||||
localSysTask ISysTask
|
||||
localSysTaskSubmission ISysTaskSubmission
|
||||
localSysAppMember ISysAppMember
|
||||
localSysUrlPool ISysUrlPool
|
||||
localSysAppMemberLoginLog ISysAppMemberLoginLog
|
||||
localSysAppMemberAuth ISysAppMemberAuth
|
||||
localSysPortal ISysPortal
|
||||
localSysDashboard ISysDashboard
|
||||
localSysBizAudit ISysBizAudit
|
||||
localSysApproval ISysApproval
|
||||
localSysAddons ISysAddons
|
||||
localSysAddonsConfig ISysAddonsConfig
|
||||
localSysAttachment ISysAttachment
|
||||
localSysBlacklist ISysBlacklist
|
||||
localSysConfig ISysConfig
|
||||
localSysCron ISysCron
|
||||
localSysCronGroup ISysCronGroup
|
||||
localSysCurdDemo ISysCurdDemo
|
||||
localSysDictData ISysDictData
|
||||
localSysDictType ISysDictType
|
||||
localSysEmsLog ISysEmsLog
|
||||
localSysGenCodes ISysGenCodes
|
||||
localSysLog ISysLog
|
||||
localSysLoginLog ISysLoginLog
|
||||
localSysNormalTreeDemo ISysNormalTreeDemo
|
||||
localSysOptionTreeDemo ISysOptionTreeDemo
|
||||
localSysProvinces ISysProvinces
|
||||
localSysServeLicense ISysServeLicense
|
||||
localSysServeLog ISysServeLog
|
||||
localSysSmsLog ISysSmsLog
|
||||
localSysTestCategory ISysTestCategory
|
||||
)
|
||||
|
||||
func SysMemberLevel() ISysMemberLevel {
|
||||
@@ -658,6 +684,33 @@ func SysAppMember() ISysAppMember {
|
||||
|
||||
func RegisterSysAppMember(i ISysAppMember) { localSysAppMember = i }
|
||||
|
||||
func SysUrlPool() ISysUrlPool {
|
||||
if localSysUrlPool == nil {
|
||||
panic("implement not found for interface ISysUrlPool, forgot register?")
|
||||
}
|
||||
return localSysUrlPool
|
||||
}
|
||||
|
||||
func RegisterSysUrlPool(i ISysUrlPool) { localSysUrlPool = i }
|
||||
|
||||
func SysAppMemberLoginLog() ISysAppMemberLoginLog {
|
||||
if localSysAppMemberLoginLog == nil {
|
||||
panic("implement not found for interface ISysAppMemberLoginLog, forgot register?")
|
||||
}
|
||||
return localSysAppMemberLoginLog
|
||||
}
|
||||
|
||||
func RegisterSysAppMemberLoginLog(i ISysAppMemberLoginLog) { localSysAppMemberLoginLog = i }
|
||||
|
||||
func SysAppMemberAuth() ISysAppMemberAuth {
|
||||
if localSysAppMemberAuth == nil {
|
||||
panic("implement not found for interface ISysAppMemberAuth, forgot register?")
|
||||
}
|
||||
return localSysAppMemberAuth
|
||||
}
|
||||
|
||||
func RegisterSysAppMemberAuth(i ISysAppMemberAuth) { localSysAppMemberAuth = i }
|
||||
|
||||
func SysPortal() ISysPortal {
|
||||
if localSysPortal == nil {
|
||||
panic("implement not found for interface ISysPortal, forgot register?")
|
||||
|
||||
Reference in New Issue
Block a user