Compare commits

...
Author SHA1 Message Date
zhangwenjian 0a629e2f3f docs📝(checksilent): describe the two passes the code actually makes
The comment claimed bindings were collected as the body was walked so that a
registration only saw definitions above it. That is the single-pass design this
started as. The code does two passes - one to collect, one to report - and a
registration therefore sees every binding in the function.

That is the point rather than an accident: a `.Use` written below a route is
still part of the chain, because the chain is assembled before anything is
served. The price is that a name reused for two different things in one
function resolves to the last assignment, which the comment now says instead of
promising an ordering the code does not keep.
2026-09-05 21:51:51 +08:00
zhangwenjian 6966f14dd4 feat✨(checksilent): report a route whose handler reads a data permission nobody supplies
GetPermissionFromContext cannot fail. When no middleware put a *DataPermission
in the context it returns the zero value, and the zero value's DataScope is the
empty string - which is not one of the five scopes Permission recognises, so it
takes the default branch and fails closed. The query is handed `1 = 0` and
matches nothing.

The endpoint then reports "not found" or "no permission" for rows that plainly
exist, and only where enabledp is true. With data permissions off - the
repository default - Permission returns the query untouched and the missing
middleware costs nothing at all. A test suite and a CI that run on the default
cannot see it.

That is what happened to /api/v1/getinfo: it read the permission on a group
carrying only the JWT middleware, so every login on a deployment with data
permissions enabled ended in a 401 from the endpoint the browser calls
immediately after signing in, and went back to the login page. Three /sys-api
routes had the same shape.

The check matches a handler to the group it is registered on, through the AST
rather than through the text - a scratch grep for the same thing reported four
false positives from a comment that happened to contain the function's name,
and before that, a dozen from matching handler names across packages. Handlers
are keyed by package, type and method, so two SysUser types are two handlers.
Subgroups inherit their parent's chain, as gin does, and a `.Use` written below
a registration still counts, because the chain is assembled before anything is
served.

Either half is a fix and the message says both, because which one is right
depends on the route. A handler reading other people's rows wants the
middleware. A handler reading the caller's own row - id from the token - wants
no scope at all: DataScopeSelf matches on create_by, so scoping a self-read
rejects every user who did not create their own account. Reporting only "add
the middleware" would have turned /getinfo from broken into worse.

Five tests: the mistake, both fixes, subgroup inheritance, and a same-named
handler in another package. TestThisRepositoryIsClean covers the real tree, and
it is what fails on the commit before this one - four findings, all real.
2026-09-05 21:31:45 +08:00
zhangwenjian 22716e90c1 fix🐛: /getinfo cannot be scoped by a data permission it never receives
Logging in on a deployment with enabledp: true ends on the login page. The
login itself succeeds - sys_login_log records it - and then /api/v1/getinfo
answers 401 "登录失败", which sends the browser straight back.

The query behind it reads:

  SELECT * FROM sys_user WHERE sys_user.user_id = 1 AND 1 = 0 AND deleted_at = 0

The 1 = 0 comes from the data-permission scope. GetInfo asked for a permission
with GetPermissionFromContext, but the group this route sits in installs only
the JWT middleware - no PermissionAction - so nothing ever put one in the
context and what came back was the zero value. An unset scope is not one of the
five recognised ones, and since unknown scopes began failing closed rather than
silently matching every row, that zero value now means "match nothing".

The route was working by accident before, and only on deployments that enable
data permissions: the repository default is enabledp: false, where Permission
returns the query untouched. That is why the local suite and CI are both green
and the demo site is not.

Two different faults, so two different fixes:

/getinfo reads the caller's own row - the id comes from the token. A data
scope answers "whose rows may this user see", so there is nothing left for it
to restrict, and applying one is not a stricter version of the query but a
broken one: DataScopeSelf matches on create_by, and an account is created by
whoever added it, so a scoped self-read would 401 every user who did not create
their own account. It now goes through GetSelf, which does no scoping at all -
which is how GetProfile has always read the same row.

/sys-api is the opposite case. Its three handlers do read the permission, and
they are listing and updating other people's rows, so the middleware belongs
there and was simply missing. Added.

Those four endpoints were found by checking every handler that reads the
permission against the group it is registered on. The check reports four before
this commit and none after.

No test. Both paths need a *gorm.DB with sys_user and sys_role rows before they
reach the line that matters, and this repository's CI has no database - `make
build` is CGO_ENABLED=0 with no sqlite tag. What can be tested is the shape of
the mistake rather than its effect, and that belongs in tools/checksilent as a
rule of its own; it is not in this commit because a site that cannot be logged
into should not wait for it.
2026-09-05 21:24:25 +08:00
wenjianzhang 73cce7fc2f Merge pull request #903 from go-admin-team/feat/005-sigterm
fix🐛: SIGTERM 从未被处理,优雅关闭在容器里是死代码
2026-09-05 18:00:58 +08:00
zhangwenjian b59c7f0d46 test✅: wait for the accept, not just the dial
Moving the dial to just before the shutdown removed one flake and introduced
another: Shutdown only waits for connections the server has already accepted,
so calling it in the gap between the dial and the accept finds nothing to
wait for and returns cleanly. The test then fails on its own "this proves
nothing" guard - which it did, after passing once.

A ConnState hook closes both gaps deterministically. The connection is opened
late enough not to age past the five seconds net/http stops counting it at,
and the child does not proceed until the server has taken it off the
listener.

Ran five times in a row rather than once, because a single green run is what
made the previous version look fixed.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 16:44:53 +08:00
zhangwenjian d3a44a2a6b test✅: dial the stalling connection after the signal, not at start-up
net/http stops counting a StateNew connection against Shutdown once it is
more than five seconds old. The connection was opened when the child started
and the parent then waited for readiness before signalling, so on a slow run
the connection could age past that mark and Shutdown would succeed - and the
test would fail on its own "this proves nothing" guard rather than on the
behaviour it is there to pin.

Opening it immediately before the shutdown keeps the timeout deterministic.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 16:42:47 +08:00
zhangwenjian 5c3c3907d5 fix🐛: arm the stop signals before announcing readiness
The previous commit split arming from waiting so a caller could arm first,
wrote a comment saying a signal landing in between reaches the default
handler and kills the process, used it that way in the subprocess test - and
then left run() calling the combined helper after the whole readiness banner.
The window it warned about was still there in the one place that ships.

The signals are now armed before the server starts serving, and the wait
happens where it did. The disposition is restored right after the first
signal rather than deferred, so a shutdown that hangs can still be
interrupted by a second one.

waitForStopSignal goes away: run() was its only caller, and what was worth
keeping from its comment is now on armStopSignals.

Note that no test covers this ordering. The subprocess test drives
armStopSignals directly, which is what makes it a test of the mechanism
rather than of run(); moving the call back below the banner leaves it green.
Verified by reading the sequence in run(), not by a failing test.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 16:42:44 +08:00
zhangwenjian f2215e132e fix🐛: handle SIGTERM, and stop exiting on a failed Shutdown
Three defects on one path, none of which could be seen from the code alone.

**SIGTERM was never registered.** signal.Notify listened for os.Interrupt
only, and Go terminates the process outright for a signal nothing handles.
`docker stop`, a Kubernetes pod deletion and `systemctl stop` all send
SIGTERM, so every line of the graceful shutdown below the wait was dead code
outside a terminal: measured on a real binary, SIGINT printed "Shutdown
Server ..." and "Server exiting" and SIGTERM printed neither.

**A stuck shutdown could not be interrupted.** quit is buffered and
signal.Notify stays armed after the first delivery, so further signals only
refill the buffer. That was harmless while SIGTERM went to the default
handler - it was the escape hatch. Registering it removes the hatch, so the
disposition is now restored once the first signal is taken, and a second one
kills the process the default way. Arming is split from waiting so a caller
can arm before it announces readiness; a signal in between reaches the
default handler, which is the very failure being fixed.

**A failed Shutdown skipped everything after it.** log.Fatal is an
unconditional os.Exit(1), and Shutdown reports an error precisely when
connections were still in flight - the moment the cleanup that follows
matters most. It is an error now, and the process carries on.

That failure is closer than it looks. net/http only treats a StateNew
connection as idle once it is over five seconds old, so a connection opened
shortly before the signal that has sent nothing holds the whole budget: with
the shipped settings.yml (readtimeout 1) the server closes it first and
shutdown takes 5ms, but with settings.demo.yml (readtimeout 10000) the same
connection made shutdown take 5.04s and exit 1, printing no "Server
exiting". The default configuration is what has been hiding this.

The wait and the shutdown are extracted so the subprocess tests can drive the
real functions against an empty http.Server: CI has no database, and none of
this needs one.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 15:28:15 +08:00
wenjianzhang a69afab34f Merge pull request #902 from go-admin-team/feat/006-contract-docs
docs📝: 契约文档指向 core,而不是宿主
2026-09-05 11:29:01 +08:00
zhangwenjian e0132db1b9 docs📝(checksilent): retire a comment that predates the lowering
The note explained the empty-shim summary as the expected state "until the
contract packages are lowered into core". They are lowered, and the shims
exist - so a count of zero now means they stopped being aliases, or stopped
being here, which is the interesting case rather than the ordinary one.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:23:31 +08:00
zhangwenjian 7c3f55a873 fix🔧(checksilent): suggest the fix with the qualifier the file uses
The contract-shim-alias message built its suggested line from path.Base of
the import path, so it told the author to write

    type ControlBy = models.ControlBy

in a file whose import is `contractmodels "…/sdk/contract/models"`. Every
shim in this repository aliases that import, so the suggestion never
compiled as written - in the one message whose whole job is to be pasted in.

qualifiedType already read the in-source identifier to resolve the import;
it now returns it.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:23:31 +08:00
zhangwenjian f7c0247394 fix🔧(checksilent): stop the seeded-value checks reporting their own tests
Widening menu-sort-overflow to see a contract MenuSpec made it fire on
app/admin/service/seed_test.go, on the case that asserts SeedMenus rejects
a sort of 900. The check was reading the proof that it works as a defect.

That is not specific to this one guard: menu-sort-overflow,
config-value-truncation, menu-id-collision and modeltime-mix are all about
a value that reaches a real database through a migration, a test fixture
reaches none, and every one of those guards needs a test that writes the
value it rejects. Skip _test.go in all four.

The two import and alias checks keep scanning tests - those are about the
dependency graph, where a test file's import is as real as any other, and
TestContractImportBoundaryCoversTestFiles already pins that.

Both directions are covered: a fixture is ignored, a real seed is still
reported.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:16:53 +08:00
wenjianzhang ac23556029 Merge pull request #900 from go-admin-team/feat/006-host-wiring
fix🐛: 契约注册面接上宿主的执行端
2026-09-05 11:14:01 +08:00
zhangwenjian f64115e03a docs📝: state what an off-convention migration file name does
The naming rule was documented; what happens when it is broken was not.
It now panics naming the offending file, which is worth saying out loud
because the alternative it replaced was silent: a name that is not a
timestamp used to register as its own version, and that migration would
never run and never report anything.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:14:01 +08:00
zhangwenjian a2524c31bf docs📝(contract): state the two menu seed rules a caller cannot infer
Sort has an upper bound: sys_menu.sort is built as a tinyint, sqlite ignores
the width, and an overflow surfaces as Error 1264 partway through a migration
rather than as a rejected value.

MenuSpec carries no menu name, and the host synthesises one from the app code
and the spec code rather than using Code directly - two applications both
choosing "list" would otherwise share a keep-alive cache key on the frontend.
Nothing in the type says so, and every Seeder implementer would have to
rediscover it.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:14:01 +08:00
zhangwenjian 71d6211c61 fix🔧(checksilent): see a menu written as a contract MenuSpec
The sort-overflow check recognised a SysMenu literal from this repository's
model packages and nothing else. An application installed from outside cannot
reach that type - it describes the same row as a seed.MenuSpec and hands it to
the host's Seeder - so the check went quiet for exactly the author furthest
from the schema it protects.

Not hypothetical: this repository's own reference application shipped a Sort
of 200, past the tinyint sys_menu.sort is built as, and this check passed it.
sqlite ignores the width, so it would have surfaced first on a real install,
as Error 1264 partway through a migration with everything after it unapplied.

The check still cannot see an application in the module cache; that half is
the Seeder's runtime validation. This closes the half that is in the tree.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:14:01 +08:00
zhangwenjian c67760bc39 docs📝(agents): match the contract rules the reference document now states
Two of the three bullets on the contract surface disagreed with
docs/contract.md and with core's own. Registration is constrained by
ordering - it must happen before the startup hooks run - not by being
written inside `init()`; and the claim that core's setters take no lock
is not true of them. The check table gains the new alias rule.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:14:01 +08:00
zhangwenjian d3e7f46a46 docs📝(contract): point applications at core, not at the host
The list of stable packages named four packages of this repository, on
the stated grounds that deduplicating app/demo's imports produces
exactly those four. That reasoning was wrong in the one direction that
matters: it sends a third-party author to depend on the host, and the
host is a fork that every user edits. `go-admin` is also not a
resolvable module path - it has no dot in its first element - so an
application cannot require it at all without a replace directive, which
is ignored outside the main module.

Rewritten around what core promises instead, and around a different
question: not "which packages does an application import" but "which
conventions fail without saying anything". Those are now spelled out
one by one, each with the mechanism that makes it silent - the response
envelope the frontend reads by `code`, the tenant-scoped connection,
`create_by` and the soft-delete marker, the data-scope middleware, the
transaction shape, and the `apps/` prefix a packaged application's menu
component must carry.

Also states two things the document was missing: installing an
application means trusting it with the host's database connection, at
the same level of trust as importing any other Go package - there is no
sandbox here and this does not pretend otherwise - and wiring an
application in touches two places, not one, where missing the second
means the migrations simply do not run, with no error.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:14:01 +08:00
zhangwenjian 55866682ae feat✨(checksilent): require a contract shim to be a type alias
A shim of a core contract type written as `type X pkg.Y` instead of
`type X = pkg.Y` keeps the fields and drops the method set, so anything
embedding it stops satisfying the interfaces it satisfied before.

The compiler catches that only where the method set is actually
exercised. This repository exercises some of the contract types through
an interface and some not at all, so the ones it does not exercise
compile here and break in a fork or a third-party application - which
is the half nobody is watching.

The trigger is the right-hand side of the declaration rather than a
list of package names, so it covers whatever the lowering ends up
shaping without a list to keep in step. Until the contract packages
land in core there is nothing here to guard, and the summary says so
rather than letting the silence read as a pass.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:14:01 +08:00
zhangwenjian 550e95ff43 docs📝: say which way sys_menu.visible points
The comment called Visible "0" "hidden by default" and then said an
administrator should not have to unhide the menu - which cannot both be
true. "0" is shown; every menu this repository seeds, including the demo
product menu that is visible on the demo site, uses it.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:08:02 +08:00
zhangwenjian 060b6cfd64 fix🐛: grant an application's apis even when it registers no menus
grantToAdminRole does two independent things - it grants the menus to the
admin role and writes a casbin rule per api - and SeedMenus skipped the
whole call whenever the menu list came back empty.

An application is free to register apis with no menus: endpoints another
service calls, a webhook, a UI mounted somewhere else. Those installs wrote
their sys_api rows and then no casbin rule for any of them, so every one of
those endpoints was denied to everyone, admin included - from a migration
that reported success and left rows in the table to prove it had run. There
is nothing to look at afterwards that says what went wrong.

Guard on both lists instead, so nothing registered stays a no-op and apis
alone still get granted.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 11:08:02 +08:00
wenjianzhang 89a4738394 Merge pull request #901 from go-admin-team/feat/006-example
feat✨: example/app-order —— 只依赖 core 的参照应用
2026-09-05 10:58:40 +08:00
zhangwenjian d8529289cf fix🐛: fold the host's GetFilename into the contract's
The host kept its own copy of the version-naming rule, byte-identical to
the one in contract/migration: slice the leading 13 characters, no check.
Two copies of a convention that applications also have to follow is two
things to keep in step, and the copies had already stopped matching - core
now rejects a name that carries no timestamp, and this one still accepted
"add_orders.go" and registered a migration under that string as its
version, which nothing would ever match and nothing would report.

Delegate instead, so there is one implementation of the rule and an app's
migration and a host migration derive their version the same way.

The test pins the reject case, not just the happy path: a re-divergence
that only sliced would still pass the happy path.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:58:39 +08:00
zhangwenjian 4a8f97b1ee feat✨: implement the menu seeder an application registers against
core's seed package defines what an application may ask for and leaves the
writing to the host, which is the only side that knows its own tables. No
host implemented it, so SeedMenus returned ErrNoSeeder and an application's
menus never appeared.

adminSeeder writes all four kinds of row, not the two an obvious reading
would stop at: without sys_menu_api_rule and the sys_role_menu / casbin_rule
grants, the menu exists and no role can reach it.

Ids are always autoincrement, never caller-assigned - checksilent's
menu-id-collision check reads literals in this repository's tree and cannot
see an application in the module cache, so the collision is removed by
construction instead of guarded. The runtime validation covers what a static
scan cannot reach for a third-party spec: duplicate codes, unresolved parents
and api references, an unknown kind, and a sort outside sys_menu.sort's
tinyint range.

MenuSpec carries no menu name, so one is synthesised from the app code and
the spec code - two applications both choosing "list" would otherwise collide
on the frontend's keep-alive key.

It lives in app/admin/service because cmd links both subcommands into one
binary, so its init runs whichever one is invoked, and cmd/migrate never has
to import app/admin to reach it.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:58:39 +08:00
zhangwenjian d54ac844ef feat✨: record which application a menu row and an api row came from
sys_migration already carries app_code; sys_menu and sys_api did not, so
nothing said which application seeded a row - which is what an uninstall or
an audit would have to ask.

The migration adds the columns through the runtime models rather than
cmd/migrate/migration/models, whose frozen ModelTime is wrong for anything
ordered after the soft-delete conversion.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:58:39 +08:00
zhangwenjian 379fba515f fix🐛: run the migrations a third-party application registers
core's sdk/contract/migration keeps its own process-wide registry, because
that is the only door open to an application that must not import the host.
Nothing here ever opened it: ForApp("crm").SetVersion(...) compiled,
registered, and then never ran - no error, no mention in status, nothing.

mergedEntries unions the host's own registry with contract/migration's
Snapshot(), and status, run and AppCodes all read through it, so migrate,
status, --dry-run and --app see an application's migrations exactly as they
see the host's. Version namespacing already keeps the two apart, so a key
collision should not be reachable; the host's own registration wins if one
ever is, rather than being silently replaced.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:58:39 +08:00
zhangwenjian 58105cb478 docs📝(example): drop a stale ordering claim from the router test
The comment said the test had to be declared first because Go runs a
package's tests in source order. That is not a guarantee, and it is not what
makes this work: the test that registers RoleCheck puts it back in a
t.Cleanup, and the guard here turns a wrong order into a loud failure rather
than a silent pass. Verified with go test -shuffle on seeds that run the two
in either order.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:53:48 +08:00
zhangwenjian 47af6f4306 fix🐛(example): make the swagger annotations resolve
The Create handler's @Param named dto.OrderCreateReq, but this file imports
that package as orderdto. swag stops on it:

    ParseComment error ... cannot find type definition: dto.OrderCreateReq

The @Success annotations name models.Response, which resolves - through
--parseDependency - to core's sdk/contract/models.Response rather than to
this package. That is the right envelope, and worth a note next to the
import, because the obvious "correction" is wrong: core's response.Response,
which the framework's own handlers name, carries no data field, so switching
to it would document these endpoints as returning no payload.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:53:45 +08:00
wenjianzhang 7d29c9953a Merge pull request #899 from go-admin-team/feat/006-jwt-hoist
fix🐛: JWT 中间件注册成了取不出来的形状
2026-09-05 10:31:44 +08:00
zhangwenjian 0729624c2f fix🐛(example): bring the directory menu's sort inside a tinyint
sys_menu.sort is `gorm:"size:4"`, which MySQL builds as a tinyint holding
-128..127. Sort: 200 passes every sqlite test - sqlite ignores the width -
and fails on a real install with Error 1264, partway through a migration.

This is the exact incident class checksilent's menu-sort-overflow check
exists to prevent, and it reached a hand-written deliverable anyway: that
check only recognises a SysMenu literal from the host's model packages, so
a seed.MenuSpec is invisible to it. Widening the check is tracked
separately; this is the value it would have caught.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:27:42 +08:00
zhangwenjian b3a740ab2a feat✨(example): register the order routes, migration and menu seed
Registration goes through core's package-level facades: SetAppRouters for
the routes, migration.ForApp for the schema, and seed.MenuSpec/ApiSpec
for the menu rows - none of which requires importing the host.

The menu component is spelled apps/order/order/index. The frontend tells
a packaged view from a built-in one by that first segment alone, and
getting it wrong is silent: the page falls back to the not-installed
placeholder while the console names a src/views path that was never going
to exist. The tests assert that prefix, that every Parent reference
closes, and that every ApiCode resolves - the three ways a menu graph is
wrong without anything saying so.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:27:41 +08:00
zhangwenjian adf617f5d0 feat✨(example): hand-write the order service and api layers
No generic CRUD action anywhere: real business - a cross-table order
placement, a payment transition - is what the contract surface has to
carry, and the actions cover only the single-table case that a real
application outgrows immediately.

The transaction is Orm.Transaction(), not the Begin/defer shape that
app/admin/service/sys_role.go and three other files use. That shape
commits a half-written transaction when the body panics, because the
deferred check reads err, which a panic leaves nil.

The payment transition guards concurrency through the update itself -
WHERE status = 'pending' plus RowsAffected - rather than a read followed
by a write.

The tests cover both rollback paths, because they fail differently: a
mid-transaction error returns, a panic unwinds - and the second is what
tells Orm.Transaction() apart from the shape it replaces. The concurrency
test pins the pool to one writer so sqlite's own single-writer semantics
cannot stand in for the guard being tested. The data-scope tests assert
the fail-closed direction too: an unrecognised scope must return no rows
rather than every row.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:27:41 +08:00
zhangwenjian 049a20cd04 feat✨(example): add the order example's models
A reference application for a third-party author: its own module, and a
require list that names go-admin-core and nothing else. The point of the
example is that constraint - an application that reaches for the host
cannot be installed through a module proxy at all, because `go-admin` has
no dot in its first path element and a replace directive is ignored
outside the main module.

Two tables rather than one, because a single-table example proves only
what the generic CRUD actions already proved. The interesting question is
whether the contract surface holds up for business that spans tables.

The table names carry an app_ prefix: "order" is a reserved word, and
Permission() interpolates the table name into raw SQL without quoting.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:27:41 +08:00
zhangwenjian be3c4452e3 test✅: pin the jwt handler being retrievable and shared
Two properties the previous shape broke silently: GetHandlerFunc must
report ok for the JwtToken key, and every module must read back the same
instance. Reverting the registration to the unbound method expression
still compiles and turns the first of these red, which is the failure
this pins.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:26:54 +08:00
zhangwenjian 5b01c9ada8 fix🐛: build the shared jwt middleware instance once in InitMiddleware
Four modules each called AuthInit and built their own instance, so which
one Runtime handed back was decided by whichever module initialised last.
The JwtToken key was also registered as an unbound method expression,
which GetHandlerFunc's type assertion can never match - the key was
registered and unusable at the same time.

The instance is now built once here and registered as a bound closure.
Modules read it back through GetAuthMiddleware, which is fatal rather
than nil when called before InitMiddleware has run: a process without a
JWT middleware should not reach the point of serving a request.

Only one call site needs the instance itself rather than the handler
(admin's /login, for LoginHandler); the thirty-odd MiddlewareFunc() call
sites are unchanged.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:26:54 +08:00
wenjianzhang ffd82a6a10 Merge pull request #898 from go-admin-team/feat/006-shim
refactor🎨: 契约包改为 core 的薄壳
2026-09-05 10:26:52 +08:00
zhangwenjian dd8d89a990 test✅: make the index probe return a copy, like every real dto.Index does
IndexAction closes over one dto.Index and serves every request to the route
from it; Generate exists so each request gets its own instance, and every
implementation in this repository returns a copy for that reason. The probe
returned the receiver, which made it the one shape IndexAction is not
written against - and inconsistent with probeRow in the same file, which
already copied.

A single-request test cannot tell the two apart, so the assertion is on
Generate itself rather than on the action's behaviour.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:22:12 +08:00
zhangwenjian 2e5b23565e test✅: cover data permission through a real CRUD action
create.go/delete.go/index.go/update.go/view.go were not lowered to
core (PRD 006 F3) and still call actions.Permission directly, in this
repository, on a code path core's own test suite knows nothing about:
core pins down what Permission builds for a given scope, but nothing
covered whether this package's five Actions still remember to call it
at all. TestIndexActionAppliesDataPermission runs IndexAction exactly
as a real request would, against a real in-memory database, and
inspects the SQL GORM actually executed - not just that the handler
returned success, which it would just as happily do with the filter
missing entirely.

The SQL is captured through a gorm.io/gorm/logger.Interface wrapper
rather than read back from IndexAction's own *gorm.DB: IndexAction
builds and executes its query in one unbroken chain
(Model().Scopes().Find()...Count()) and never hands the built
statement back to its caller, so there is nothing else to inspect it
through.

Counterproof performed and reverted (not part of this commit): with
Permission(object.TableName(), p) removed from IndexAction's Scopes
call, the test failed with the captured SQL carrying no WHERE clause
at all (`SELECT * FROM action_probe_row LIMIT 10`); index.go was then
restored to its committed content (`git diff --exit-code` verified
clean).

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:15:58 +08:00
zhangwenjian f4e3f04d30 refactor🎨: turn common/actions' data permission into a thin forward
DataPermission, PermissionAction, Permission, GetPermissionFromContext,
IsValidDataScope, PermissionKey and the five DataScope* constants now
forward to go-admin-core's sdk/contract/actions, which carries the
already-fixed logic from feat/006-security-prereq (PRD 006 F14/H1-H3).
create.go/delete.go/index.go/update.go/view.go - the five generic CRUD
actions - are untouched: they call Permission and
GetPermissionFromContext by the same names, which now resolve to
forwards with identical behaviour, and stay in this package rather
than moving to core (PRD 006 F3: core's exports are a permanent
promise every fork inherits, and CRUD shape is this framework's most
volatile surface).

PermissionKey is declared as `const PermissionKey =
contractactions.PermissionKey`, a direct reference rather than a
restated literal, per PRD 006's hard constraint 4: PermissionAction
sets this gin context key and GetPermissionFromContext reads it back,
and an independently declared copy could silently drift from core's if
one were ever edited without the other. permission_test.go replaces
the detailed data-permission regression suite - which now lives in
core, next to the logic itself - with a test of this package's own
wiring: that PermissionAction and both of this package's own read
paths (GetPermissionFromContext, and c.Get(actions.PermissionKey)
directly) still meet on the same key.

Counterproof performed and reverted (not part of this commit): with
PermissionKey redeclared here as the literal "dataPermission" and
core's copy changed to a different value, TestPermissionKeyMatches-
WhatPermissionActionSets went red while GetPermissionFromContext's own
round-trip stayed green - confirming the exported constant, not the
GetPermissionFromContext wrapper, is what an independent literal would
put at risk.

PRD 006 F3/F5.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:15:58 +08:00
zhangwenjian 954ebdc9eb refactor🎨: turn common/dto into a thin alias of go-admin-core
AutoForm, ObjectById/ObjectGetReq/ObjectDeleteReq, Pagination,
GeneralDelDto/GeneralGetDto and Index/Control are now type aliases of
go-admin-core's sdk/contract/dto; OrderDest, MakeCondition and
Paginate forward to the same package (functions cannot be aliased the
way types can).

MakeCondition no longer reads common/global.Driver to choose which SQL
dialect to resolve search tags against. The lowered version reads
db.Dialector.Name() from inside the closure it returns instead, which
is always the driver the caller's own *gorm.DB is bound to - correct
even with more than one database open with different drivers, which a
single process-wide variable could never be. global.Driver is marked
Deprecated accordingly; it is still set and still readable for fork
code that reads it directly.

PRD 006 F2/F5.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:15:58 +08:00
zhangwenjian 2840010dfd refactor🎨: turn common/models into a thin alias of go-admin-core
ControlBy, Model, ModelTime, ActiveRecord, BaseUser, Response, Page,
Migration and the menu type constants now read `type X = pkg.X` /
`const X = pkg.X` against go-admin-core's sdk/contract/models instead
of defining these shapes locally. Every embed, GORM tag and JSON tag
is unchanged - a type alias is the same type, not a new one - and
every existing import of go-admin/common/models keeps compiling with
no changes of its own (verified with `git diff --exit-code` over the
70 files that import common/models, common/dto or common/actions).

The menu type constants (Directory/Menu/Button) are declared as direct
references rather than restated literals: an independently written
copy of the same value can be edited out of step with go-admin-core's,
where a direct reference cannot (PRD 006 hard constraint 4).

PRD 006 F1/F5.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:15:51 +08:00
zhangwenjian b147d9b833 build🔧(deps): require go-admin-core v2.5.0
v2.5.0 carries the sdk/contract packages the commits that follow alias
common/models, common/dto and common/actions onto.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-05 10:15:33 +08:00
wenjianzhang b3ecb81614 Merge pull request #896 from go-admin-team/fix/sys-user-privesc
fix🐛: 修复 sys-user 更新接口的垂直越权
2026-09-05 00:59:59 +08:00
wenjianzhang ce4581bb99 Merge pull request #897 from go-admin-team/feat/006-security-prereq
fix🐛: 数据权限的三处静默失效
2026-09-05 00:59:12 +08:00
zhangwenjian f406ca0160 test✅: fail loudly instead of skipping when the sqlite setup breaks
The privilege-escalation tests skipped themselves when opening the in-memory
database or running AutoMigrate failed. Both depend on nothing outside the
process, so a failure there means the environment is genuinely broken - and a
security regression that quietly does not run is worse than one that is
missing, because CI stays green either way.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 19:10:53 +08:00
zhangwenjian 39ea1f6aef fix🐛: normalize a NULL data scope too, not just an unrecognized one
`NULL NOT IN (...)` evaluates to NULL rather than true, so the previous
condition left a NULL data_scope exactly as it found it - and NULL is the one
value that most needs the repair: it scans into a Go string as "", which is
what the fail-closed default now refuses.

The column has no NOT NULL constraint, so the value is reachable from any
writer that is not the admin UI.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 19:10:53 +08:00
zhangwenjian b2053f507a refactor🎨: drop the unused second data permission implementation
app/admin/models/datascope.go carried a second copy of the scope logic with no
callers. Its department-tree pattern was written as "%" + id + "%" instead of
"%/" + id + "/%", so dept_id 1 also matched /11/, /21/ and /100/ - visibility
into unrelated subtrees.

It sat where someone looking for a data permission example would find it. The
copy that is actually wired up stays in common/actions.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:55 +08:00
zhangwenjian 9520117914 feat✨: normalize invalid data scopes on existing installs
The seed fix only reaches new installations. An install that imported the old
db.sql already has an administrator with an empty data_scope, and after the
fail-closed change that account sees nothing.

The migration rewrites any value outside "1".."5" to "1", which is the
behaviour those rows had before. Plain SQL rather than the frozen migration
models, per the rule that migrations after 1786700003000 must not use them.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:55 +08:00
zhangwenjian 7a5fc7d440 fix🐛: give the seeded admin role an explicit data scope
The shipped seed data left data_scope empty for the built-in administrator.
That was harmless while an unrecognized scope meant "see everything"; with the
previous commits it means the opposite, so a fresh install with data permission
enabled would have blinded its own default account on every list endpoint.

The admin short-circuit does not help here: role_key == "admin" bypasses Casbin,
not the data permission scopes, which never look at role_key.

The value is "1" - all data - which is the behaviour the empty string used to
produce, so this restores the intent rather than tightening it. A test reads
both seed files back so the pair cannot drift apart again.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:41 +08:00
zhangwenjian bd5e83d464 fix🐛: validate data scope on the role DTOs
Nothing checked what went into sys_role.data_scope, so creating a role without
a dataScope stored an empty string - the value that used to be indistinguishable
from "see everything".

All three DTOs that write the column are validated, not just the insert path:
they target the same column, and guarding one entrance while leaving two open
would not be a guard.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:41 +08:00
zhangwenjian 63dd40a8d7 test✅: cover the data scope failure directions
A table over all five scopes plus the ones that are not scopes, asserting the
generated SQL rather than a boolean, because the defect was that two different
intentions produced the same query.

The rows that matter are the negative ones: an unrecognized value, a zero
value, and a department scope with a non-positive id. Each was verified to go
red with its own fix reverted and the others in place, so a regression names
the defect it belongs to.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:41 +08:00
zhangwenjian 32bd88504d fix🐛: fail closed when the department id is not positive
dept_path is built as "/0/" + id + "/..." for every department, so a DeptId of
0 turns the department-tree pattern into '%/0/%' - which matches every row in
sys_dept. The scope meant to narrow visibility to one subtree returned the
whole organisation instead.

Both department scopes now refuse a non-positive id rather than building a
pattern from it. The admin DTO validates deptId, but seed scripts, SSO and
third-party registration paths do not, and after the contract move the caller
is no longer ours to control.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:26 +08:00
zhangwenjian d70818a9db fix🐛: fail closed on an unrecognized data scope
The switch ended in `default: return db`, which is the same answer as "this
role may see everything". That made a legitimate scope indistinguishable from a
broken one: "1" (all data) had no case of its own and fell into default too, so
"1", "", "6" and a zero value all produced byte-identical SQL.

Three changes, in this order, because reversing them would break "1":

  - the five scope values become named constants, so a reader can tell which
    string means what without consulting the seed data
  - "1" gets an explicit case, which is what frees default to mean "not a
    scope I recognise"
  - default now matches nothing rather than everything

SysRole DTOs accept the scope unvalidated, so an empty string reaches this
switch from ordinary use, not just from a corrupted row.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:26 +08:00
zhangwenjian 9d4a425fc0 fix🐛: abort the request when the ORM is unavailable
PermissionAction logged the error and returned. Gin treats a plain return as
"carry on", so the request reached the business handler with PermissionKey
never set - and a zero DataPermission means Permission() adds no WHERE clause
at all. A database hiccup turned into full visibility, silently.

The neighbouring newDataPermission branch already aborts. This one now does the
same.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:24:12 +08:00
zhangwenjian 4d6456a588 test✅: cover vertical privilege escalation on sys-user update
Two directions, because the fix has to hold both: an attacker with no policy on
this route cannot raise another user's role, and a self-edit cannot raise its
own. The second one is what keeps the fix from being "just remove the route
from CasbinExclude", which would break the profile page.

The tests drive the handler directly rather than through the router, because
the middleware is exactly what does not run for this route - the defence lives
in the handler, so that is where it has to be proven.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:23:07 +08:00
zhangwenjian 07ff92aa55 fix🐛: lock privileged fields on self-edit
The profile page posts the whole user object back, including roleId, deptId and
status, because it renders from a full SysUser it fetched earlier. A caller
editing their own record can therefore hand back a tampered roleId.

Self-edits now reload those three fields from the database and ignore whatever
the request carried. For an honest client this is a no-op - the values it sends
are already its own - so the profile page keeps working unchanged.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:23:07 +08:00
zhangwenjian 4156387eb9 fix🐛: enforce Casbin when editing another user
PUT /api/v1/sys-user sits in CasbinExclude so the profile page can reach it,
which means AuthCheckRole never runs for this route. The handler took the
target user id from the request body, so any authenticated caller could edit
another user's record - including their roleId.

The route has to stay excluded: the profile page and the admin user list share
this one endpoint, so removing the exclusion would break self-service editing
for every non-admin role. The check therefore moves into the handler: when the
target is not the caller, the request is put through Casbin explicitly.

EnforceRoleFor carries the same admin short-circuit and enforcement AuthCheckRole
uses, so a route that opts out of the middleware can still ask the same question.

Claude-Session: https://claude.ai/code/session_01HPTAw8b8tAdFNFn8rKdPYx
2026-09-04 17:23:01 +08:00
wenjianzhang 34773a0a81 Merge pull request #894 from go-admin-team/chore/core-v2.4.1
Bump go-admin-core to v2.4.1
2026-09-01 20:47:11 +08:00
zhangwenjian 36a018400b chore🔧(deps): bump go-admin-core to v2.4.1
Documentation wording only; no code change between the two.
2026-09-01 20:42:36 +08:00
wenjianzhang 7bb02c5f1d Merge pull request #893 from go-admin-team/docs/contract-wording
Describe the rules rather than who follows them
2026-09-01 20:38:34 +08:00
zhangwenjian 15fb128236 docs📝: describe the rules rather than who follows them
The warning on Authorizator matters to anyone keeping a copy of that file, not
to one particular consumer, and it reads better addressed to all of them: check
what reads those context keys before taking this change.
2026-09-01 19:56:27 +08:00
wenjianzhang 3581e060ec Merge pull request #891 from go-admin-team/feat/003-app-prep
Groundwork for installable applications
2026-09-01 19:38:23 +08:00
zhangwenjian 0604a29596 feat✨(server): run the startup hooks through core
The package-level AppRouters slice keeps working and keeps running first, so
a fork that only ever appended to it sees no change. What is new is that the
core registry runs too, and that before callbacks run at all - this server
never had a loop for them.

Both go through core RunAppRouters / RunBefore, which brings the panic guard
and the seal with them.
2026-09-01 18:18:38 +08:00
zhangwenjian d8a2958797 chore🔧(deps): bump go-admin-core to v2.4.0 2026-09-01 18:18:38 +08:00
zhangwenjian ab28fa7bed docs📝: write down what a third-party app may depend on 2026-09-01 17:45:41 +08:00
zhangwenjian e88d751039 chore🔧: run the silent-failure checks in CI 2026-09-01 17:45:41 +08:00
zhangwenjian b836945eea feat✨: add checksilent, for the failures that do not report themselves
Six checks, five at ERROR and one - the cross-repository menu-name comparison -
at WARN, because it can only match by regular expression across two modules and
a false positive that fails CI teaches people to silence the tool.

The summary names which contract roots were actually scanned: core/ is a
separate module with no directory here, and a check that quietly covers less
than it claims is worse than no check.
2026-09-01 17:45:41 +08:00
zhangwenjian d7a8e66753 feat✨: add migrate status, --dry-run and --app
--app rejects a code nothing was registered under, on all three paths. It used
to take a typo as "nothing matched" and report success: migrate said the app
was unknown and still exited 0, while --dry-run and status printed the same
words an up-to-date database produces.
2026-09-01 17:45:40 +08:00
zhangwenjian 68780a845c feat✨: register migrations under an app code with ForApp 2026-09-01 17:45:40 +08:00
zhangwenjian 487dc94a2e feat✨: record on sys_migration which app a migration belongs to 2026-09-01 17:45:40 +08:00
zhangwenjian 016e977776 refactor🎨: drop Authorizator assertions that never matched
The map Authorizator receives is built by IdentityHandler in the same file and
carries IdentityKey / UserName / RoleKey / UserId / RoleIds / DataScope - not
user and role. Both assertions failed on every request, and because the ok
result was discarded the five c.Set calls stored zero values and the function
returned true anyway. Nothing in this repository or in core reads those keys.

go-admin-pro has its own copy of this file and does read them; this change
must not be carried over there verbatim.
2026-09-01 17:45:40 +08:00
zhangwenjian dcfe512204 refactor🎨: move the operation log status constants out of app/admin
common/middleware imported app/admin/service/dto for two string constants,
which made a package apps are told to build on depend on one particular app.
2026-09-01 17:45:40 +08:00
zhangwenjian fe6ebfd47c chore🔧: ignore the Go workspace files and the checksilent binary
go.work points this module at a local checkout of go-admin-core while the
two are developed together. It is a local tool and must never be committed:
CI resolves core from go.mod.

checksilent is where `go build ./tools/checksilent` drops its binary - four
megabytes beside the server one, which was already ignored by name.
2026-09-01 17:45:40 +08:00
wenjianzhang eba5fba3da Merge pull request #889 from go-admin-team/fix/password-hook-and-body-buffering
fix: a password hook that could destroy credentials, and a body copy on every request
2026-09-01 14:22:13 +08:00
zhangwenjian b7e9a79225 fix🐛: refuse an out-of-scope API update with the permission message
The previous commit added a data-permission scope to SysApi.Update and
returned early on db.Error, which left the RowsAffected check below it
unreachable: First reports a row the scope excluded as ErrRecordNotFound,
so the caller got "record not found" where the code meant to say
"无权更新该数据".

Map that one error to the permission message and drop the check it made
dead. The two cases - the row does not exist, and the row exists but is
not yours - have to look the same from outside, and now do.

Found by Copilot's review of #889.
2026-09-01 14:17:20 +08:00
wenjianzhang deffb19fd8 Merge pull request #888 from go-admin-team/ci/run-tests
Run the test suite in CI
2026-09-01 11:38:10 +08:00
zhangwenjian c858b322bd fix🐛: apply the data permission when updating an API
SysApi.Update took a DataPermission and never used it, so with data
permission enabled the update reached rows the caller could not read
through GetPage, Get or Remove, which all scope the query. It also
reported "无权更新该数据" for a row that simply did not exist, a message
that only becomes true once the scope is applied.

Drops the Debug() left on the query, which logged the statement for
every call.
2026-09-01 11:35:51 +08:00
zhangwenjian 1b5b52f0f1 perf👌: only read the request body when the operation log will store it
LoggerToFile is registered on the engine, so every POST, PUT, GET and
DELETE had its body copied into memory - through a bytes.Buffer, a
ReadAll and a string conversion - before any handler ran. The only
consumer is operParam on the operation-log row, which is written when
logger.enableddb is on, and that is off in the shipped configuration.

There was no size limit either, and a file upload is a POST like any
other: a 1MB request allocated 4.3MB here and a 16MB upload allocated
about 67MB, to build a value nobody stored.

The body is now read only when the operation log will use it, and at
most 32KB of it. The handler still receives the whole request: it reads
the copied part from memory and the rest from the connection, so what
this holds is bounded however large the request is. 32KB also keeps the
value inside the TEXT column it is written to.

The bufio.Writer this replaces was never flushed. Nothing was truncated
only because bytes.Buffer implements io.ReaderFrom, so io.Copy bypassed
the buffer entirely - a different destination would have dropped the
tail of every request body.
2026-09-01 11:35:45 +08:00
zhangwenjian ecb31a158b fix🐛: stop re-hashing a password that is already hashed
BeforeCreate and BeforeUpdate run Encrypt on whatever is in the struct,
and a user read from the database carries the stored hash in Password.
Hashing it again produces a hash of a hash: the password that user knows
stops matching, they cannot log in, and nothing reports an error.

Only the Omit("password") on SysUser.Update stood between that and the
stored credential. Any other write to this model - a profile update
written the way every other model here is written - destroys the
password, permanently and silently.

Encrypt now returns early when Password already parses as a bcrypt hash.
That also removes the round SysUser.Update was paying and discarding:
306ns where it was 54.7ms, on a route reachable without the permission
check, since PUT /api/v1/sys-user is in CasbinExclude.

The cost of deciding from the value is that a password which is itself a
well-formed bcrypt hash would be stored unchanged. That is a
60-character string beginning "$2a$", and it grants whoever set it no
access they did not already have.
2026-09-01 11:35:33 +08:00
zhangwenjian 1aecc140dc ci🔧: run the test suite on every push and pull request
The repository has 19 test files and nothing was running any of them. Both
workflows build with go build, which does not compile _test.go, the Makefile's
test target was commented out, and there is no pre-commit hook. Every test in
the tree, including the schema guards that exist precisely to catch a silent
breakage, only ran when someone remembered to type go test.

Enables the commented-out target and calls it from go.yml, the one workflow
that fires on every push and pull request. build.yml is left alone: it skips
documentation-only changes and deploys on master, so it is the wrong place for
a gate that should never be skipped.

Runs with -race. common/actions reuses model instances across concurrent
requests, so a Generate() that returns in place rather than a copy leaks data
between them, which a single-threaded run cannot see.

Verified locally: the suite passes under CGO_ENABLED=0 and under -race, and
make test exits non-zero when a test fails, so the step actually gates.

Claude-Session: https://claude.ai/code/session_01DJhM6LvhkNPej35wy9F7Aq
2026-09-01 11:33:08 +08:00
wenjianzhang e464a4aedd Merge pull request #887 from go-admin-team/fix/migration-model-soft-delete-drift
Guard new migrations against the frozen seed models
2026-08-31 15:06:05 +08:00
wenjianzhang 595c4a6be5 Merge pull request #885 from go-admin-team/fix/casbin-tenant-and-pattern-cache
fix: key the casbin enforcer by tenant, and stop recompiling patterns in the exclusion scan
2026-08-31 15:01:42 +08:00
zhangwenjian d115c5299c docs📝: say which models package a new migration may seed through
The hazard had no signal at its point of contact. Someone adding a business
module is told to copy 1786700001000_demo_menu.go, which imports the frozen
seed models - correct for that file, wrong for anything ordered after the
soft-delete conversion. The frozen ModelTime carried no comment at all, so
opening it taught the reader nothing.

Documents the boundary in three places the author actually passes through:
the frozen type itself, the contributor guide, and the module-scaffolding
skill, which previously said to copy the reference file verbatim and now
says to copy its structure but not its imports.

Claude-Session: https://claude.ai/code/session_01DJhM6LvhkNPej35wy9F7Aq
2026-08-31 15:01:26 +08:00
zhangwenjian 9bd542bb59 test✅: guard post-conversion migrations against the frozen seed models
Migrations ordered after 1786700003000 must not seed through
cmd/migrate/migration/models. That package's ModelTime declares a nullable
gorm.DeletedAt, which is the shape the columns had until that migration
converted deleted_at to a NOT NULL millisecond marker.

Afterwards it breaks in both directions. Writes put NULL into a NOT NULL
column and fail on the first insert. Reads are scoped "WHERE deleted_at IS
NULL" while live rows hold 0, so they match nothing - and 1786700001000
looks the admin role up that way and treats ErrRecordNotFound as "roles are
not seeded yet, skip authorisation", which would leave a module seeded with
no permissions and the migration still recorded as applied.

A fresh database does not surface either one: every migration using that
package today is ordered before the conversion, so it runs while the column
is still nullable. Only a migration added afterwards hits it.

Also pulls the import scan out of importsRuntimeModels so both checks share
one implementation, and derives the version through migration.GetFilename
rather than a second filename-parsing rule.

Verified by adding a violating migration and confirming the test fails with
an actionable message, then removing it and confirming the suite passes.

Claude-Session: https://claude.ai/code/session_01DJhM6LvhkNPej35wy9F7Aq
2026-08-31 15:01:11 +08:00
zhangwenjian 0fa015b6d0 perf👌: stop recompiling patterns when scanning the casbin exclusion list
AuthCheckRole walks CasbinExclude for every non-admin request, and used
casbin's util.KeyMatch2 to test each entry. That delegates to
util.RegexMatch, which is regexp.MatchString - it compiles its pattern on
every call - so a 32-entry list cost about 2,566 allocations per request
before the request reached Enforce.

Test the method first, which rules out most entries with a string
compare, and take the path test from go-admin-core, whose KeyMatch2
answers the same thing without recompiling. The scan drops to 52ns and no
allocations.

The loop moves out of AuthCheckRole so the tests exercise the code a
request runs rather than a copy of it, and an allocation budget fails if
the uncached matcher comes back.
2026-08-31 14:01:32 +08:00
zhangwenjian ec7d838ebd fix🐛: key the casbin enforcer by tenant host
setupSimpleDatabase runs once per configured database - one per host in
the multi-tenant configuration - and passed the same empty key to
mycasbin.Setup every time. Setup caches per key, so every host after the
first was handed the enforcer built from the first host's database and
was authorized against a casbin_rule table that was not its own.

Takes effect with the go-admin-core release that keys the cache; before
it, Setup ignored the argument entirely.
2026-08-30 10:03:26 +08:00
wenjianzhang 26e116c16c Merge pull request #884 from go-admin-team/ci/skip-deploy-for-docs
ci🔧: skip the deploy workflow for documentation-only changes
2026-08-29 14:49:39 +08:00
zhangwenjian 90d98893f5 ci🔧: skip the deploy workflow for documentation-only changes
A push to master here does not just build: it pushes an image, runs the
migrations and restarts the demo container, so the site takes a short outage
each time. The last two merges were markdown only and both paid for it.

Beyond the waste, a deploy can fail for reasons unrelated to the change that
triggered it - a container that will not come up, a database that is briefly
unreachable - and a README edit should not be able to turn the demo red.

Only build.yml is filtered. go.yml still builds on every push and pull request,
so nothing loses its compile check, and the badge keeps reporting the same
workflow it reports today.
2026-08-29 14:29:05 +08:00
wenjianzhang 10f162bf5d Merge pull request #883 from go-admin-team/fix/readme-vitepress-syntax
docs📝: drop VitePress container syntax GitHub cannot render
2026-08-29 14:28:11 +08:00
zhangwenjian 19909746f5 docs📝: drop VitePress container syntax GitHub cannot render
`:::tip` and its closing `:::` are VitePress custom containers. GitHub has no
such syntax, so both markers rendered as literal text: a paragraph beginning
":::tip" and a stray ":::" sitting alone above the next heading.

The Chinese README carries the same warning as a plain paragraph, which GitHub
renders correctly, so the English one now matches it. Verified through GitHub's
own markdown API: the literal marker no longer appears in the output and the
warning survives as ordinary text.

Only README.md was affected; the other three never had it.
2026-08-28 20:36:24 +08:00
wenjianzhang 205febdb8a Merge pull request #882 from go-admin-team/docs/readme-links-and-languages
docs📝: fix the badges and links, add Traditional Chinese and Japanese READMEs
2026-08-28 20:32:13 +08:00
zhangwenjian 5aec4ba32b docs📝: add Traditional Chinese and Japanese READMEs
The project had English and Simplified Chinese. These two follow the same
structure - same sections, same code blocks, same contributor list - so a
reader in any of the four sees the same document.

The Traditional Chinese is a translation rather than a character conversion of
the Simplified: the terminology differs (設定檔, 資料庫, 選單, 程式碼產生,
排程任務, 相依套件), and a converted file would read as machine output to
anyone who actually uses it.

Language navigation across all four is unified in the same commit, since a link
to a file that does not exist yet would be worse than no link.
2026-08-28 20:19:50 +08:00
zhangwenjian 8f1ea50dfe docs📝: point the badges and documentation links at the right places
The build badge rendered "build - failing" on both READMEs while CI was green.
It referenced the workflow under the old personal repository path, where the
status has been stale for years - so the first thing anyone saw on opening the
project was a failed build. It now points at the current repository, names the
workflow file explicitly, and pins the branch, so it reports master rather than
whatever happens to be the default branch later.

The workflow it reports on is go.yml, which is what the old badge referenced
by workflow name and is the right one to show: build.yml also deploys the demo
site, so a server-side problem there would turn the badge red while the code
is fine.

The licence badge read from mashape/apistatus, the example repository from
shields.io's own documentation. It happened to show MIT, the same licence this
project uses, so nobody noticed - but it reports someone else's licence.

Documentation links were spread across three hosts: doc.go-admin.dev redirects
to www.go-admin.pro, www.go-admin.dev serves byte-identical content, and only
the Chinese README linked the canonical host at all. All of them now point at
www.go-admin.pro directly rather than relying on a redirect outliving the
domain that issues it.

Two smaller ones: the gorm link pointed at the archived v1 repository while the
project builds on gorm.io v2, and the English introduction listed two UI kits
where the Chinese listed three, with an Ant Design demo linked directly below.
2026-08-28 20:19:50 +08:00
wenjianzhang 1483ca401d Merge pull request #881 from go-admin-team/fix/production-defaults
fix🐛: the settings a deployment needs, and the ones that were leaking
2026-08-28 19:50:18 +08:00
zhangwenjian ed74623a73 test✅: add an end-to-end load test harness
Skipped unless GOADMIN_BENCH_ADDR points at a running server, so `go test
./...` is unaffected.

Reports latency percentiles rather than an average, which is what capacity
planning needs, and a status-code distribution - that last part is how the rate
limiter's 200-on-rejection was found, since throughput alone looked excellent
while nothing reached a handler.

Includes a routing-floor control case. When a business endpoint matches it, the
measurement has stopped describing the endpoint and started describing the
transport, or the load generator when both share a machine.
2026-08-28 19:42:38 +08:00
zhangwenjian 1bc2e22833 fix🐛: give the config templates the defaults a deployment actually needs
Two settings that decide whether a deployment survives load, neither of which
appeared in any template.

The connection pool. Left unset, Go's defaults apply, and MaxIdleConns is 2:
under load almost every request opens a TCP connection and closes it again,
local ports run out, and the process answers "can't assign requested address"
to everything. Not slower - unavailable. A sweep against MySQL collapsed to
zero successful responses at 64 concurrent requests without these, and served
13,846 req/s with no errors once they were set.

The queue buffer. poolSize is the point at which messages start being dropped,
not a tuning knob: a full queue discards the message and returns an error
rather than blocking, and each stream has one consumer goroutine writing to the
database. At the previous default of 100 a load test lost over 60% of them; at
1000, none. Login and operation logs travel this queue, so what gets lost is
audit data - though only when logger.enableddb is on.

Both carry the reasoning in the file, because the failure mode of each is
invisible until it happens in production.
2026-08-28 19:42:38 +08:00
zhangwenjian cd8edfa5d4 fix🐛: reject rate-limited requests with 429 and make the threshold configurable
A rejected request answered 200 with the failure only in the body, so every
layer that reads the status line counted it as served: load balancers, metrics,
client-side retry. A load test against this reported the limiter's own
rejections as successful traffic and overstated throughput more than tenfold.

The threshold was a constant in the middleware, which made 200 QPS the ceiling
of every deployment with nothing in the configuration to reveal it. It now
reads extend.rateLimit.inboundQPS; an absent value keeps 200, so an upgrade
changes nothing, and zero disables the limiter for a deployment behind its own
gateway.

Also drops Strategy: system.BBR. Reading sentinel's source, the adaptive
strategy is consulted only for Load and CpuUsage - for InboundQPS the trigger
count is compared directly - so it read as if the limit adapted to the machine
when it never did.
2026-08-28 19:42:21 +08:00
zhangwenjian dcc2c8e175 fix🔒: stop logging the captcha answer
The answer was written at info level on every captcha request, so a currently
valid answer sat in the application log. Anyone able to read the log - an
operator, a log aggregator, anything that ships logs off the host - could
bypass the check the captcha exists to enforce.

The default log level records it, so this was not limited to debug builds.
2026-08-28 19:42:21 +08:00
zhangwenjian d991a285ba chore🔧: upgrade go-admin-core to v2.2.0
Carries four concurrency fixes and a bounded in-memory cache. The two that
reach this repository are the search resolver, which no longer panics on an
unexported field in a DTO and skips tag parsing for zero-valued ones, and the
captcha driver, which is built once rather than per request.

The cache bound does not apply here: config.CacheConfig.Setup() returns the
older Memory implementation, which core leaves unbounded.
2026-08-28 19:42:19 +08:00
wenjianzhang 76c9d1211e Merge pull request #880 from go-admin-team/fix/dsn-in-logs
ci🔧: migrate before deploying, roll back on failure — and stop logging the database password
2026-08-27 17:31:52 +08:00
zhangwenjian f5273f5a58 ci🔧: migrate before deploying, and roll back when the new version does not come up
Closes #871.

The deploy did docker rm -f then docker run. Nothing ran migrations, so
new code met old tables, and nothing checked the result - a container
that exits immediately left the site down with a green deploy.

Now, in order: pull the image, run the migration with it, and only then
touch what is running. A failed migration stops there, leaving old code
with the old schema, which is at least self-consistent.

The running container is renamed rather than removed, so it can be
started again unchanged if the new one does not become healthy. Healthy
means both an HTTP response and a database connection in the log: the
captcha endpoint answers without touching the database, so it alone
would call a container healthy that cannot reach MySQL.
2026-08-27 16:19:03 +08:00
zhangwenjian 54ffaac9c5 chore🔧: say which migration is running, not a column of ones
An applied migration printed its count - a bare '1' - so a database with
seven of them wrote seven lines of '1' at every start, and a failure said
only which error, never which migration.

It now names each one as it applies, reports the total, and says so when
there is nothing to do.
2026-08-27 16:19:03 +08:00
zhangwenjian 484de2e698 fix🔒: stop writing the database password into the log
The startup line printed the DSN whole:

  * => goadmin:<password>@tcp(host:3306)/go-admin?...

So every deployment wrote its own database credential into its own logs,
where a log shipper, a support bundle or a screenshot of a terminal
carries it onward. Found while reading deploy output, which is exactly
how it leaks.

The host and username stay - they are what makes the line worth printing
- and only the password is replaced. Both DSN shapes this project accepts
are covered, a sqlite path is left alone, and anything unparseable is
withheld rather than echoed, since it may hold a credential too.
2026-08-27 16:17:12 +08:00
wenjianzhang d72ff76aad Merge pull request #879 from go-admin-team/ci/config-path-secret
ci🔧: keep the host config path out of a public repository
2026-08-27 15:41:54 +08:00
zhangwenjian 4a523bed92 ci🔧: keep the host config path out of a public repository
The path is not a credential, and the file it points at is 600 and owned
by root, so this is not what protects it. But the repository is public
and there is no reason to publish the server's directory layout next to
the deploy that uses it.

DEMO_CONFIG_PATH holds it instead. It has to be set before this merges,
or the deploy stops at the guard - which is the intended failure: better
that than falling back to the sqlite in the image.
2026-08-27 15:37:09 +08:00
wenjianzhang 55dc33b865 Merge pull request #878 from go-admin-team/ci/demo-on-mysql
ci🔧: run the demo on the managed database instead of a bundled sqlite file
2026-08-27 15:33:15 +08:00
zhangwenjian 3d13f5856a ci🔧: point the demo at the managed database
The demo ran on the sqlite file baked into the image, so every deploy
reset it and nothing there resembled how anyone actually runs this.

The config is mounted from the host rather than taken from the image.
config/settings.demo.yml ships in a public repository and is copied into
a public image, so the connection string cannot live there; that copy
stays on sqlite, which is what a fresh clone should get.

The deploy refuses to start if the host config is missing, rather than
falling back to the image's sqlite and looking like it worked.
2026-08-27 12:48:32 +08:00
wenjianzhang aa2976ba17 Merge pull request #877 from go-admin-team/fix/mysql-fresh-install
fix🐛: MySQL installs could not log in — the migration run stopped at a tinyint overflow
2026-08-27 12:18:04 +08:00
zhangwenjian 8e141ff8a0 fix🐛: the code generator listed no tables at all
sys_columns and sys_tables were left out of the soft-delete conversion in
1786700003000. Their runtime models embed common.ModelTime, which is the
millisecond marker, so GORM queries them with deleted_at = 0 - against a
nullable datetime column holding NULL. Every row was invisible.

The repository carries two ModelTime types: the one under
cmd/migrate/migration/models still has a nullable gorm.DeletedAt and is
what builds the tables, while common/models has the marker and is what
queries them. Nothing connected the two, so a table could be built one
way and read the other with no signal at all.

The test now walks app/ for models embedding the marker and requires a
migration to cover each. tb_demo is exempt and says why: nothing reads it
at runtime.
2026-08-27 12:12:13 +08:00
zhangwenjian 2628ab8e3e fix🐛: a seeded menu overflowed its column and stopped the migration run
sort is gorm:"size:4", which MySQL builds as a tinyint holding -128..127.
The demo menu seeded Sort: 900, so on MySQL the run stopped at
1786700001000 with Error 1264, and every migration after it - including
the soft-delete conversion - never ran.

deleted_at therefore stayed NULL while the code queries deleted_at = 0,
and the login returned 'incorrect Username or Password' on a database
whose password hash was correct all along.

sqlite ignores the declared width, so a fresh install there passed and
the fault only appeared on MySQL.
2026-08-27 12:12:00 +08:00
wenjianzhang 1b7dcd843c Merge pull request #876 from go-admin-team/perf/data-permission
perf👌: the data-permission lookup ran on every request, including when it was switched off
2026-08-24 16:11:47 +08:00
zhangwenjian f0d91fb763 perf👌: read the data scope from the token instead of joining for it
The scope is decided by the user id, the role id, the department and the
data_scope string. Three of the four were already in the token; deptid
was not, though core's user.GetDeptId has always read that claim. Adding
it removes a sys_user join from every list, detail, update and delete.

This goes no more stale than rolekey does, which Casbin has read from
the token since the beginning: both settle on the next login.

A token minted before this still works. Its claims are incomplete, and
the lookup runs for it as before.
2026-08-24 15:36:11 +08:00
zhangwenjian 7238c6a26d perf👌: stop looking up a data scope that is switched off
Permission() returns the query untouched when EnableDP is false, so the
lookup feeding it has nothing to feed. The lookup ran anyway: a sys_user
join against sys_role on every list, detail, update and delete, with the
result discarded.

enabledp is false in settings.full.yml, so this was the default.
2026-08-24 15:35:33 +08:00
wenjianzhang 0964cf98e2 Merge pull request #875 from go-admin-team/fix/file-store-nil-client
fix🐛: the upload endpoint panicked on source=2, and cloud storage was never wired up
2026-08-24 15:00:31 +08:00
zhangwenjian 04c6a081ae fix🐛: source=3 uploaded to aliyun, and neither provider was ever configured
thirdUpload dispatched on the source parameter and then built the same
zero-value ALiYunOSS in both branches, so source=3 could not have
reached qiniu even with credentials.

Neither branch had credentials to use. OXS.Setup is the initialisation
path and nothing in the repository called it, and no configuration field
existed to fill. The store is now taken from extend.fileStore, and a
provider that was not configured says so rather than producing the
provider's own complaint about an empty bucket name.

The two handlers passed errors.New("") to e.Error, discarding what
actually went wrong; they now pass the error.
2026-08-24 13:25:54 +08:00
zhangwenjian fcbd9ae02e fix🐛: an unconfigured object store reports it instead of panicking
Each implementation keeps its provider client in an interface{} field that
Setup assigns, so an unconfigured store holds nil - and asserting nil to
the provider's client type panics:

  panic: interface conversion: interface {} is nil, not *oss.Client

The upload endpoint reaches that path for any request naming a provider
the deployment never configured.

Three more things were wrong in the same files. OXS.Setup printed a
failure and returned the store anyway, handing back exactly the broken
object that panics. HuaWeiOBS.UpLoad printed the provider's error and
returned nil, so a failed upload reported success. Both it and
QiNiuKODO.UpLoad asserted the local path was a string without checking.

The tests asked the reader to paste their own credentials, so they failed
for everyone who did not. They now cover the guards and skip the part
that needs a provider unless credentials are in the environment.
2026-08-24 13:23:07 +08:00
wenjianzhang d34d30a197 Merge pull request #874 from go-admin-team/ci/serialize-deploys
ci🔧: run one deploy at a time
2026-08-23 14:15:21 +08:00
zhangwenjian ecfea845c2 ci🔧: run one deploy at a time
Two merges seconds apart raced. Both runs do docker rm -f then docker
run; the second removed the container the first had just created, and
the first's docker run failed on the name conflict:

  Conflict. The container name "/go-admin-api" is already in use

The deploy went red and the demo stayed on the older image, which is the
worse half: a failure that leaves the wrong version running.

Grouping by ref serialises pushes to master while leaving pull request
runs independent, since those carry their own ref.
2026-08-23 14:11:49 +08:00
wenjianzhang e05ff7c809 Merge pull request #873 from go-admin-team/chore/drop-dockerfilebak
chore🔧: delete Dockerfilebak
2026-08-23 14:03:34 +08:00
wenjianzhang 28a9626661 Merge pull request #872 from go-admin-team/chore/skill-new-business-module
docs📝: add the new-business-module skill, and keep the rest of .claude out
2026-08-23 14:03:28 +08:00
zhangwenjian 96b2cb3acf chore🔧: delete Dockerfilebak
Added in 2022 and never touched since. Nothing references it - not the
workflows, not the Makefile, not a script - and it could not build
anyway: it copies config/settings.yml out of the builder, and that file
is gitignored.

It is a leftover from when the image was built inside the container,
before that was replaced by copying a binary built on the runner. Its
MAINTAINER line was the last one in the repository; Docker deprecated
the instruction in favour of LABEL maintainer years ago.
2026-08-23 13:43:40 +08:00
wenjianzhang 87fe6b7d9b Merge pull request #864 from go-admin-team/chore/core-v2
chore🔧: move to go-admin-core v2
2026-08-23 13:43:19 +08:00
zhangwenjian 722de8ea65 chore🔧: move the generator templates to v2 as well
The code generator writes Go files, and its templates still spelled the
old import paths, so a module generated after this migration did not
compile: the router it emits declares InitBusinessRouter with the v1
*GinJWTMiddleware while common.AuthInit now returns the v2 type.

Two of the paths moved rather than gaining a /v2 segment - the jwtauth
and response shims under sdk/pkg are gone in v2 - so this is not the
same rewrite the Go files got.
2026-08-23 13:28:17 +08:00
zhangwenjian 8ffde94433 chore🔧: move to go-admin-core v2
Every import of the module changes, not only the seven packages that
moved out of sdk/pkg: Go requires the major version in the path from v2
on. Both happen in one pass —

    go run github.com/go-admin-team/go-admin-core/tools/coreupgrade@v2.0.0 -w -v2 .
    go mod tidy

— which is the command the release notes give, run here as a consumer
would run it. 210 imports across 95 files.

The compatibility shims this used are gone in v2, so the paths that
moved had to move: sdk/pkg/captcha, sdk/pkg/jwtauth and its user
package, sdk/pkg/response and sdk/pkg/casbin.

The count of unformatted files is unchanged at 34, none of them touched
by this: the tool reformats a file only if it was already gofmt clean,
so a migration cannot disappear into whitespace.
2026-08-23 13:26:46 +08:00
wenjianzhang 08eef12bca Merge pull request #865 from go-admin-team/fix/codegen-inverted-guards
fix🐛: the code generator's guards were all written backwards
2026-08-23 13:24:50 +08:00
zhangwenjian ab0e8e6056 docs📝: add the new-business-module skill, and keep the rest of .claude out
The skill walks a single-table CRUD module end to end: migration, the
Actions-mode model, dto and router, and the sys_menu / sys_api /
casbin_rule seed data without which the module builds but never appears.

.claude was ignored wholesale. Un-ignoring the skills directory would
have committed every skill put there, including personal ones, so the
skills that ship are re-included one directory at a time.

AGENTS.md now points at 1786700001000_demo_menu.go for the seed data,
which is the runnable version of what the skill describes.
2026-08-23 13:20:26 +08:00
zhangwenjian 2cef52d906 chore🔧: record the modules the code imports as direct
go mod tidy moves glebarez/sqlite and gorm.io/plugin/soft_delete out of
the indirect block: the tests import the first and common/models the
second. CI runs tidy before building, so the tree was dirty from the
first command.
2026-08-23 13:19:15 +08:00
zhangwenjian 95077b116b fix🐛: the candidate table query assumed one database
The exclusion list was a subquery against `$GenConfig.DBName`.sys_tables,
so it named the schema by hand. Generating from a schema that is not the
one holding sys_tables made the whole query fail, and because the
subquery read the table directly it also counted soft-deleted entries:
deleting a generator entry never handed its table back.

Read the registrations through the model on this connection instead. An
empty list skips the clause - NOT IN (NULL) is unknown for every row,
which would leave a fresh install with nothing to generate from.
2026-08-23 13:19:15 +08:00
zhangwenjian d4cf11d313 fix🐛: report an unknown database driver instead of panicking
opens is a map, so opens[c.Driver] on a driver this build does not carry
returns a nil function, and gorm.Open calls it. The operator saw a nil
dereference inside gorm with nothing naming the driver.

sqlite3 is the case that bites: it needs cgo and is only compiled in
under the sqlite3 build tag, so the same config file works on one binary
and dies on another. Resolve the driver first and say which ones this
build supports.
2026-08-23 13:19:15 +08:00
zhangwenjian f201792d8f fix🐛: the mysql-only guard never fired
pkg.Assert panics when its condition is false, so pkg.Assert(true,
"目前只支持mysql数据库") is a no-op. On postgres or sqlserver the code
generator did not report that it needs MySQL: DBTables returned an empty
list with a nil error, and DBColumns ran its query on the zero-value
*gorm.DB left over from the branch that never assigned, which is a nil
dereference rather than a message.

Assert the driver up front instead of asserting a constant in an else,
which also removes the placeholder *gorm.DB the fall-through relied on.
DBColumns.GetPage had no guard at all and gets the same one.
2026-08-23 13:19:15 +08:00
zhangwenjian d16f5e7180 fix🐛: db columns endpoint rejected exactly the valid requests
pkg.Assert panics when its condition is false, so
Assert(TableName == "", "table name cannot be empty") rejected every
request that carried a table name and let the empty one through. The
model layer repeated the inversion with if TableName != "" { return
error }, so either one alone was enough to break the endpoint.

Flip both, and hoist the model guard out of the mysql branch so it
matches GetList ten lines below, which had it right all along.
2026-08-23 13:19:15 +08:00
wenjianzhang f8f697af2b Merge pull request #868 from go-admin-team/ci/stop-gitee-mirror
ci🔧: stop mirroring the repository
2026-08-23 13:18:19 +08:00
wenjianzhang eb8da38a46 Merge pull request #870 from go-admin-team/fix/demo-db-soft-delete
fix🐛: the soft-delete migration could not run, and the demo database never got it
2026-08-23 13:01:17 +08:00
zhangwenjian f19568c69a chore🔧: bring the bundled demo database up to the current migrations
go-admin-db.db ships in the repository and the Dockerfile copies it into
the image, which then runs only the server. Its last recorded migration
was from 2022, so every row still carried a null deleted_at while the
code queries deleted_at = 0. Nothing matched: not the login, not the
sixty-seven menus, not the five departments.

Anyone starting from the bundled sqlite database met the same wall, and
the failure reads as an incorrect username or password.
2026-08-23 12:57:05 +08:00
zhangwenjian 91bf25e5fe fix🐛: the soft-delete migration could not run against the real schema
Two assumptions held on the test's table and on nothing else.

It dropped deleted_at while an index still referred to it. MySQL and
PostgreSQL drop dependent indexes along with the column; SQLite refuses,
and the migration stopped at the first table with such an index - which
is all thirteen of them.

It also read the rows through a column named id. sys_dept keys on
dept_id, sys_user on user_id, and only some tables on id, so the pass
that carries the deletion timestamps across never ran.

The test's table had an id key and no index on deleted_at, which is
exactly the shape that lets both through. It now matches sys_user.
2026-08-23 12:56:50 +08:00
wenjianzhang 85666f160f Merge pull request #867 from go-admin-team/docs/readme-refresh
docs📝: repoint the README links that stopped resolving
2026-08-22 23:12:49 +08:00
zhangwenjian b2baf48dc6 ci🔧: stop mirroring the repository
Every push mirrored to Gitee and GitLab. Neither mirror is wanted any
more, so the workflow goes rather than half of it.

The GITEE_KEY and GITLAB_KEY secrets are left in place; restoring the
mirror is a revert of this commit.
2026-08-22 17:34:50 +08:00
zhangwenjian c906e1d503 docs📝: repoint the links that stopped resolving
The two tutorial links pointed at doc.zhangwj.com, which no longer
answers; the same paths serve from doc.go-admin.dev. golangroadmap.com
returns 503. The jwt-go credit pointed at dgrijalva/jwt-go, archived
years ago - this project builds on golang-jwt/jwt.

Also: the copyright years said 2022 and 2024, the English README asked
for a password in Chinese, and the Chinese README's link section lost
its only entry, so it gets the one the English side already had.
2026-08-22 15:26:44 +08:00
wenjianzhang 3b93ac19f8 Merge pull request #863 from go-admin-team/fix/soft-delete-groundwork
fix🐛: a unique constraint the database can actually keep
2026-08-22 11:59:12 +08:00
zhangwenjian 9914373d45 test🧪: run the assertion through the function it is about
Review caught that this reissued getByRoleName's query instead of
calling it, so it passed whether or not the production line still said
what it was supposed to — a test named for a change it did not touch.

It calls getByRoleName now, and restoring the hand-written clause fails
it for exactly the reason this PR exists: with the marker non-null,
"deleted_at is null" matches nothing and the query returns an empty
list.
2026-08-22 11:40:38 +08:00
zhangwenjian 4911730012 fix🐛: give the natural keys a constraint the database can keep
sys_user.username, sys_role.role_key and sys_dict_type.dict_type had no
unique index. Uniqueness was a SELECT COUNT followed by an INSERT, which
two concurrent requests both pass — and login resolves a username with
First, so which of the two accounts answers is whichever the database
returns.

The index cannot be on the key alone, because a soft-deleted row keeps
occupying the name and a deleted user's username could never be used
again. It has to include the delete marker, and the marker has to be
non-null: two live rows are (alice, NULL) and (alice, NULL), and NULL is
not equal to NULL, so an index over a nullable marker admits both. That
is the worst of the three states — a constraint that reads as protection
and binds nothing — and there is a test that demonstrates it rather than
asserting it.

ModelTime.DeletedAt is milliseconds since the epoch now, zero while the
row is live. Sixteen tables carry it; the migration converts each one,
preserving when each deleted row was deleted, then adds the three
indexes.

Written to be re-runnable rather than transactional, because DDL does not
roll back on MySQL and an operator whose first attempt failed halfway
should have nothing to do but run it again. It refuses before altering
anything if a table already holds duplicates, naming them, rather than
letting the index fail and leaving the operator to guess.

The timestamp conversion happens in Go: turning a timestamp into epoch
milliseconds is spelled differently by every dialect this supports, and
these row counts do not justify four versions of it.
2026-08-22 11:27:22 +08:00
zhangwenjian 88bab51056 fix🐛: stop hand-writing the soft-delete condition, and check the count
Two things in front of the unique-index work, both safe on their own.

getSysMenuByRoleName carried "deleted_at is null" in its where clause.
GORM adds that condition itself for a model with a DeletedAt field, so
it was a duplicate — and one phrased as a column being null, which stops
being true the moment the column stops being nullable. A schema that
moves to a non-null delete marker would have turned this query into one
that matches nothing, silently, for admin users only.

SysDictType.Insert dropped the error from its duplicate check: a query
that failed left the count at zero and the insert went ahead as though
the name were free.

The test pins what the removed clause was there for. Its counter-proof
is Unscoped rather than deleting the field — taking ModelTime off the
model fails to compile, which proves nothing.
2026-08-22 11:11:03 +08:00
wenjianzhang 0fd4f68b6c Merge pull request #861 from go-admin-team/docs/fix-stale-queue-redis-sample
fix: correct the commented-out queue.redis sample in settings.yml
2026-08-20 11:01:03 +08:00
zhangwenjian c66cb5c6a8 fix: correct the commented-out queue.redis sample in settings.yml
The sample had producer/consumer nested keys (streamMaxLength,
approximateMaxLength, visibilityTimeout, bufferSize, concurrency,
blockingTimeout, reclaimInterval) that don't exist on config.RedisQueue —
checked against sdk/config/queue.go, which only reads addr, password, and the
embedded RedisOptions fields, plus group, key_prefix and max_attempts. Filling
in the old sample as written would compile and start fine, since it's YAML
under a key the struct doesn't declare, and every one of those settings would
be silently ignored.

Replaced with the fields the struct actually has. Still commented out —
redis stays opt-in, this only fixes what filling it in would produce.
2026-08-20 10:54:53 +08:00
wenjianzhang b16ec0af77 Merge pull request #860 from go-admin-team/chore/upgrade-core
chore🔧: upgrade go-admin-core and route the queue through configuration
2026-08-18 22:48:00 +08:00
zhangwenjian 82ea8539eb chore🔧: upgrade go-admin-core and route the queue through configuration
The pinned core dated from April, before sdk stopped being a separate module,
so the build resolved sdk packages from the old module and core packages from
the new one. Dropping the separate requirement is what makes the two agree
again.

Most of the diff is renames that came with that: the tenant accessors gained a
ByTenant suffix, GetDb now returns one database and GetAllDb the map, and
casbin moved to v3.

The change that matters is four call sites moving from GetMemoryQueue to
GetQueuePrefix. GetMemoryQueue returns a queue fixed at construction, so the
login log, the operate log and the api check ran in process no matter what the
settings file selected — a second instance saw none of it. GetQueuePrefix
returns whatever the configuration built, which is the point of being able to
configure a queue at all.

Verified against core at main: build and vet clean. The two file_store failures
are unchanged from before this branch; they need cloud credentials.
2026-08-18 22:14:08 +08:00
wenjianzhang d17d5c1206 Merge pull request #859 from go-admin-team/docs/clarify-demo-sites
docs📝: 标注 antd 演示站对应 go-admin-pro
2026-08-16 19:31:25 +08:00
zhangwenjian 041d22d0d2 docs📝: 标注 antd 演示站对应 go-admin-pro
README 中两个演示地址并排列出、格式与账号密码完全相同,看不出 antd 站对应
的是另一个产品。用户在该站遇到问题时会认为是本仓库的缺陷(见 #857:登录
返回的错误码在本仓库中并不存在)。

仅在链接文字中补充产品名,不改变呈现方式。
2026-08-16 12:04:26 +08:00
wenjianzhang 5864058a81 Merge pull request #858 from go-admin-team/chore/bump-version-2.4.0
chore🔧: 版本号升至 2.4.0
2026-08-16 11:17:51 +08:00
zhangwenjian 45035a16e4 chore🔧: 版本号升至 2.4.0 2026-08-16 11:14:28 +08:00
wenjianzhang f4d0108d49 Merge pull request #855 from go-admin-team/fix/remove-refresh-token-endpoint
fix🐛: 移除 refresh_token 接口,修复 token 可无限续期问题
2026-08-16 11:08:41 +08:00
zhangwenjian b81611ba72 chore🔧: 清理 refresh_token 的残留权限数据
接口移除后,库中仍留有三类记录:sys_api 的接口登记、sys_menu_api_rule 的
菜单绑定、casbin_rule 的策略。留着会让「接口管理」列出一个不存在的端点,
角色配置里也仍可勾选。

- 新装:从 db.sql 与 db-sqlserver.sql 的种子数据中删除该接口
- 已有部署:新增迁移清理,按 path 匹配而非固定 id,因为执行过
  `server -a` 重新注册接口的库中 id 会与官方种子数据不同
2026-08-14 21:42:59 +08:00
zhangwenjian bb34108831 fix🐛: 移除 refresh_token 接口,修复 token 可无限续期问题
close #820

GET /api/v1/refresh_token 用业务 token 即可换取新 token,而续期上限
MaxRefresh 依据的 orig_iat 在每次续期时被一并重置,上限永远无法到达 ——
token 一旦泄露即等同于永久访问权,且无任何吊销手段。

该路由此前还位于 CasbinExclude 中,不受 Casbin 约束,任何角色的已登录用户
都可调用。

官方前端从未使用它:store 中虽有 refreshToken action,但全仓库无一处
dispatch,属死代码。移除不影响正常登录与鉴权流程。

破坏性变更:自行调用该端点实现续期的使用者需改为重新登录。正确的无感续期
应在 go-admin-core 中区分 access token 与 refresh token 后重新实现,不应
沿用此路由。
2026-08-14 21:42:52 +08:00
wenjianzhang b7fd92f39b Merge pull request #854 from go-admin-team/docs/agents-and-demo-module
feat✨: 新增 app/demo 参照模块与 AGENTS.md 规范文档
2026-08-14 21:37:57 +08:00
zhangwenjian 63b800a3ba docs📝: 补充 sqlite3 构建标签与迁移目录说明
driver 配置为 sqlite3 时不带 -tags sqlite3 会在 nil 函数上 panic,
报错不提及构建标签,容易误判为环境损坏;同时说明 version/ 与
version-local/ 的区别,后者已被 gitignore,提交到本仓库的迁移必须放 version/。
2026-08-14 21:17:37 +08:00
zhangwenjian ed9450a2d5 feat✨: 补充 demo 模块的菜单与权限种子数据
一个业务模块要在界面上可用,需要四类数据协同:

  sys_api           后端路由登记,Casbin 据此判定
  sys_menu          侧边栏菜单,含目录 M、菜单 C、按钮 F 三级
  sys_menu_api_rule 菜单与接口的关联,角色保存时据此生成策略
  casbin_rule       实际生效的权限策略

菜单的 menu_name 与前端组件 name 保持一致(DemoProduct),按钮的
permission 与前端 v-permisaction 标识一致(demo:product:add 等)。

策略写入 casbin_rule 而非 sys_casbin_rule:后者对应的 models.CasbinRule
是历史遗留,其 7 列 size:512 唯一索引在 MySQL 下会超出索引长度限制,实际
生效的是 adapter 创建的 casbin_rule 表。

所有写入均为存在则更新、不存在则插入,迁移可安全地在已有数据的库上执行。
实测:在含 67 条菜单、121 条接口的库上执行后各表数据正确;清除版本记录重
跑一次,各表行数不变,确认幂等。
2026-08-14 16:57:55 +08:00
zhangwenjian 1d551a10ab docs📝: 新增 AGENTS.md 与架构说明
AGENTS.md 是给 AI 编码工具与新贡献者的约定,只记录「不遵守就会出错」的
规则,技术栈版本与命令交由 go.mod 和 Makefile 表达,避免文档与代码脱节。
标准写法指向 app/demo/——那是可编译、有测试的参照物,文档与它冲突时以它
为准。

docs/architecture.md 承载不易从代码直接读出的语义:DataScope 五档的过滤
方式、定时任务的 JobExec 接口、多数据源约束、迁移目录的分工。

内容整理自此前未纳入版本控制的 CLAUDE.md,撰写时逐条对照代码核实,修正
了其中两处失效描述(构建工具已非 Vue CLI;JobExec 的方法是 Exec(interface{})
而非 Run(string))。CLAUDE.md 现改为指向 AGENTS.md 的软链,两者不再各自
漂移。
2026-08-14 16:49:48 +08:00
zhangwenjian 4d7c9e5a12 feat✨: 补充 demo 模块的建表迁移
放在 version/ 而非 version-local/:后者已被 .gitignore 忽略,是留给使用
者存放自身迁移脚本的位置,示例迁移需随框架一起分发。文件注释中说明了这
一区分。
2026-08-14 16:46:53 +08:00
zhangwenjian d1f5fe5681 feat✨: 新增 app/demo 标准 CRUD 参照模块
作为编码约定的可执行参照物:文档会滞后,而这个模块过时会导致构建或测试
失败,因此以它为准。

目录骨架与自动注册文件由项目自带的脚手架生成:

  go run main.go app -n demo

它同时产出 cmd/api/demo.go,其中的 init() 将路由追加进 AppRouters,
无需在任何中心文件手工登记。

模块本身演示了单表 CRUD 的推荐写法——直接使用 common/actions 提供的五个
通用 Action,因此只有 model、dto、router 三个业务文件,没有 apis 与
service。手写 Handler 的场景仅在业务超出单表 CRUD 时才需要。

DTO 中详情/删除入参内嵌 dto.ObjectById 以复用其 Bind 与 GetId,不重复
实现 uri 绑定与批量 ids 合并逻辑。

补充 8 项测试锁定通用 Action 的接口约束,其中最关键的是 Generate() 必须
返回副本——Action 在并发请求间复用实例,就地返回会串数据。反向验证:将
Generate 改为就地返回,测试立即失败。
2026-08-14 16:46:09 +08:00
zhangwenjian e8c2e0a966 chore🔧: 修正 .DS_Store 忽略规则
原规则 `*/.DS_Store` 只匹配子目录一层,仓库根目录下的 .DS_Store 不在其
中。改为 `.DS_Store`,匹配任意层级。
2026-08-14 16:45:55 +08:00
wenjianzhang cef0a19a9c Merge pull request #853 from go-admin-team/fix/community-pr-batch
fix🐛: 处理社区 PR 中仍然成立的四项修复
2026-08-14 15:46:43 +08:00
zhangwenjian c0e81363dc docs📝: 修正 Makefile 注释错别字
「实际决对路径」→「实际绝对路径」。

问题由 PR #847 指出。
2026-08-14 15:35:55 +08:00
zhangwenjian df2e4a2b48 fix🐛: 修正欢迎页 iframe 高度塌陷
页面通过 JS 计算并设置 iframe 高度,但 html 与 body 未声明高度,
百分比高度失去参照,iframe 在部分场景下塌陷为 0。

补充 html,body{height:100%} 与 iframe 的 height:100%,并为原先缺失的
overflow-y 声明补上分号。

问题由 PR #829 指出。
2026-08-14 15:35:55 +08:00
zhangwenjian 9088ebc2e1 refactor🎨: 修正文件名拼写 int_router.go → init_router.go
该文件内容为 init() 函数中的路由注册,原文件名少了一个字母。

问题由 PR #787 指出。
2026-08-14 15:35:55 +08:00
zhangwenjian 9f2dec3036 fix🐛: 修正 GeneralDelDto.GetIds 重复追加 Id
该方法先在开头追加了 Id,随后 else 分支中又追加一次:仅传 Id 时返回
[5 5],删除接口会对同一条记录执行两次 DELETE。

  if g.Id != 0 { ids = append(ids, g.Id) }
  if len(g.Ids) > 0 { ... } else {
      if g.Id > 0 { ids = append(ids, g.Id) }   // 重复
  }

去掉冗余分支,同时将首个判断由 != 0 收紧为 > 0,与 Ids 中逐个元素的
过滤条件保持一致(负数 Id 无意义)。

补充单元测试,覆盖仅 Id、仅 Ids、二者并存、含非正数、全空回退等场景。

问题由 PR #848 指出。
2026-08-14 15:35:55 +08:00
wenjianzhang ea049f9b06 Merge pull request #852 from go-admin-team/fix/ci-deploy-guard
fix🐛: 限制部署步骤仅在 master 收到 push 时执行
2026-08-12 15:41:26 +08:00
zhangwenjian 1f1349a685 docs📝: 更新在线体验地址
Element UI vue2 演示站已升级为 Element Plus + Vue 3,域名同步更换为
vue.go-admin.pro。

Arco Design vue3 演示站(vue3.go-admin.dev)已下线,移除对应条目。
2026-08-12 12:27:41 +08:00
zhangwenjian dcef2df38e fix🐛: 限制部署步骤仅在 master 收到 push 时执行
本工作流同时由 push 与 pull_request 触发,而推送镜像与重启服务两步没有
任何事件限制。其后果是:任何指向 master 的 PR 一经创建,就会把 PR 分支
构建出的镜像推送到镜像仓库,并 docker rm -f 掉线上容器、用该镜像重新启
动 API 服务——发生在代码被审查和合并之前。

同仓库分支发起的 PR 可以取到 secrets,因此该路径实际可达;历史运行记录
中已多次出现由 pull_request 事件触发的成功部署。

为两步加上 event_name 与 ref 双重判断。额外判断 ref 是考虑到日后若有人
向 on.push.branches 追加分支,部署不会随之扩散。

Tidy 与 Build 不受影响,PR 仍会执行编译校验。
2026-08-12 12:27:33 +08:00
zhangwenjian 92834d6e39 publish🚀: 版本号更新至 2.3.0 2026-08-12 00:22:31 +08:00
zhangwenjian f06540883b fix🐛: 修复 Docker 镜像发布的 tag 条件失效问题
if 表达式中不应使用 ${{ }} 包裹:startsWith(${{github.ref}}, 'refs/tags/')
会先将 github.ref 替换为裸字符串再参与表达式求值,导致条件判断失效,
使得每次 push 到 master 都会构建并推送镜像至 ghcr.io,而非仅在打 tag 时发布。

同时 on.push 缺少 tags 配置,打 tag 实际不会触发该工作流。

修正后:push 分支仅执行 Go 构建,打 tag 才发布镜像。
2026-08-11 11:09:39 +08:00
zhangwenjian 3c9ce5b6b0 chore🔧: 升级 x/image 修复 TIFF 解码漏洞 2026-08-10 22:17:28 +08:00
zhangwenjian ff8a59550a fix🐛: 修复镜像同步因浅克隆被拒绝的问题 2026-08-10 21:21:01 +08:00
zhangwenjian 7cddef33a2 git🙈: 将 go.sum 纳入版本控制 2026-08-10 20:51:00 +08:00
zhangwenjian 7013c2fa4a chore🔧: 移除依赖已封禁 action 的 issue 自动化流程 2026-08-10 20:51:00 +08:00
zhangwenjian 65bacacb38 docs📝: 更新 README 环境要求版本说明 2026-08-10 20:45:39 +08:00
zhangwenjian 45587028c3 config🔧: CI 升级 Go 版本并更新 Actions 至最新 2026-08-10 20:45:39 +08:00
zhangwenjian 887c9cca4b chore🔧: 升级 Go 至 1.26.5 并同步升级依赖 2026-08-10 20:45:36 +08:00
wenjianzhang a6ddb113fc Update LICENSE.md 2026-08-08 13:55:08 +08:00
wenjianzhang b83eef8670 Fix image source in README.md
Updated image source in README.md for go-admin.
2026-05-22 11:20:39 +08:00
zhangwenjian 43dcd61c51 config🔧: update go-version to 1.24 in build workflow
go.mod requires go 1.24, go mod tidy fails when runner uses 1.18.
2026-05-15 17:52:12 +08:00
zhangwenjian 1bd64d4562 config🔧: pin all GitHub Actions to full-length commit SHAs
Replace version tags (@v1/@v2/@v3/@master) with pinned commit SHAs
across all workflow files to satisfy go-admin-team organization
security policy requiring immutable action references.
2026-05-15 17:48:41 +08:00
zhangwenjian 44e81bc72f git🙈: 补充忽略本地开发配置文件
- 新增忽略 config/settings.local.dev.yml
2026-05-15 17:37:42 +08:00
zhangwenjian 3312f8b7b9 chore🔧: 升级依赖 mergo 模块路径
- 替换 github.com/imdario/mergo 为上游迁移后的 dario.cat/mergo v1.0.1
2026-05-15 17:37:42 +08:00
zhangwenjian d6a2272f9d git🙈: 完善 .gitignore 忽略规则
- 新增忽略编译产物 go-admin-server
- 新增忽略本地工具配置目录
2026-05-15 17:37:42 +08:00
wenjianzhang a5cc0a9e29 Add read and write timeout to HTTP server 2025-09-10 09:39:54 +08:00
wenjianzhang 3f995735e9 Merge pull request #834 from hosea3000/edit-no-confirm
点击编辑的时候不需要弹框确认,交互不太友好
2025-05-20 11:41:02 +08:00
wenjianzhang b65b74dee5 Merge pull request #832 from hosea3000/fix-number-input
fix🐛: 修复自动生成代码时选择字段类型为int64, 前端提交还是string 导致报错的问题
2025-05-20 11:40:21 +08:00
Hosea 98cf3ad95a fix🐛: 点击编辑的时候不需要弹框确认,交互不友好 2025-05-20 10:57:36 +08:00
Hosea 8649d8d791 fix🐛: 修复自动生成代码时选择字段类型为int64, 前端提交还是string 导致报错的问题 2025-05-13 15:48:45 +08:00
wenjianzhang 817e34c6aa refactor🎨: 重构文件上传逻辑,拆分处理函数以提高可读性和维护性 2025-04-13 22:20:06 +08:00
wenjianzhang 952cd92648 refactor🎨: 清理 sys_server_monitor.go 文件,移除未使用的导入并格式化代码 2025-04-13 22:17:17 +08:00
wenjianzhang 6b1e961a7f refactor🎨: 重构系统监控代码,拆分功能为多个函数以提高可读性和维护性 2025-04-13 22:15:10 +08:00
wenjianzhang 762eba5af7 refactor🎨: 重构 Setup 函数,拆分为多个子函数以提高可读性和维护性 2025-04-13 22:04:12 +08:00
wenjianzhang e82128f679 refactor🎨: 优化获取客户端 IP 的逻辑,增加对 X-Forwarded-For 和 X-Real-IP 的处理 2025-04-13 22:00:04 +08:00
wenjianzhang 8f8a197db1 delete🎉: 移除示例代码 run.go 2025-04-08 20:49:36 +08:00
wenjianzhang 364854eda0 docs📝: 更新 go-admin 版本号至 2.2.0 2025-04-08 20:49:29 +08:00
wenjianzhang b259e91f4d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	go.mod
2025-04-08 20:45:25 +08:00
wenjianzhang 76411f80bc refactor🎨: 优化日志记录方式,统一使用 log.Info 替代 log.Println 2025-04-08 20:30:35 +08:00
wenjianzhang 5494353229 fix🐛: 修复获取本地主机IP的函数调用错误 2025-04-08 20:30:24 +08:00
wenjianzhang afe5efbe36 refactor🎨: remove unused distributed lock setup code in initialize.go 2025-04-08 20:30:05 +08:00
wenjianzhang db422785fc fix🐛: include captcha answer in GenerateCaptchaHandler for improved logging 2025-04-08 20:29:38 +08:00
wenjianzhang 4ac68323da fix: improve error logging in jobbase.go for better clarity 2025-04-08 20:23:23 +08:00
wenjianzhang 44002fcb11 chore: update dependencies in go.mod to latest versions 2025-04-08 20:22:59 +08:00
wenjianzhang 5bbd919745 chore: update dependencies in go.mod to latest versions 2025-03-25 17:16:40 +08:00
wenjianzhang 54dd3de5b6 chore: update dependencies in go.mod to latest versions 2025-03-25 16:48:47 +08:00
wenjianzhang 9540fdfc30 refactor: remove unused GetMenuIDS function and clean up code 2025-03-25 16:44:31 +08:00
wenjianzhang 937775e2a7 chore: update Go version from 1.21 to 1.24 in build configuration 2025-03-25 08:53:39 +08:00
wenjianzhang 84721265dd fix: simplify error handling in GenerateCaptchaHandler 2025-03-24 22:41:02 +08:00
wenjianzhang 3ab67dfa7d chore: update Go version from 1.21 to 1.24 2025-03-24 20:53:09 +08:00
wenjianzhang 6a1941a820 Merge pull request #814 from keemozhang/master
fix🐛: declaration of new local variable causes transactions to be ign…
2025-03-21 15:36:16 +08:00
wenjianzhang 3ae7c44585 Merge pull request #816 from Tiper-In-Github/patch-1
Fix:err is never used
2025-03-21 15:35:23 +08:00
wenjianzhang 9d809f6392 Merge pull request #821 from pigwantacat/master
fix:修复定时任务的日志打印
2024-12-18 00:11:54 +08:00
pigwantacat 4b477b3103 fix:修复定时任务的日志打印 2024-11-01 14:14:30 +08:00
wenjianzhang e7ae2fe019 更新 go_admin.go 2024-10-30 22:12:17 +08:00
wenjianzhang d5ba3d9770 更新 READMEN.md 2024-10-30 22:10:06 +08:00
Akiraka f3d744f6f5 修复获取getinfo时候,userName 事件结果为 nickName 问题 2024-10-24 09:30:15 +08:00
无别 0315631b53 Fix:err is never used
Fix the problem that err is overwritten and becomes invalid
2024-09-29 15:32:36 +08:00
wenjianzhang 48e7ce88ff perf👌: rollback base64Captcha 2024-09-09 15:46:05 +08:00
wenjianzhang 357db6b1c9 Merge remote-tracking branch 'origin/master' 2024-09-08 22:04:20 +08:00
wenjianzhang 83e0531f43 perf👌: format 2024-09-08 22:04:08 +08:00
wenjianzhang 898ba7d8eb Update README.Zh-cn.md 2024-09-06 23:11:48 +08:00
wenjianzhang 8751f34539 perf👌: correct attribute definition 2024-09-05 18:33:11 +08:00
wenjianzhang 4aa0068d2d perf👌: update SysDept Get First to FirstOrInit 2024-09-05 18:29:50 +08:00
keemozhang b954a2f092 fix🐛: declaration of new local variable causes transactions to be ignored 2024-09-05 15:46:20 +08:00
wenjianzhang 9227bd2be1 perf👌: format code 2024-09-04 20:25:02 +08:00
wenjianzhang e70a0b1314 perf👌: update SysConfig Get First to FirstOrInit 2024-09-04 20:22:49 +08:00
wenjianzhang 21c262a31e perf👌: update build file 2024-09-03 22:17:11 +08:00
wenjianzhang f30889bd19 perf👌: update SysApi Get Func First to FirstOrInit 2024-09-03 21:22:09 +08:00
wenjianzhang 23e519999e perf👌: update go mod 2024-08-30 16:00:29 +08:00
wenjianzhang bedf064ace Merge pull request #802 from zhanluxianshen/drop-base-model
replace basemodel by common.model
2024-08-29 16:26:22 +08:00
wenjianzhang 9a8e0cddde Merge pull request #803 from zhanluxianshen/clean-err-use-in-method
clean err define in methods.
2024-08-29 16:23:54 +08:00
wenjianzhang c0c16036d3 Merge pull request #811 from wangle201210/fix/logger
fix🐛: reset default logger fields
2024-08-29 16:17:18 +08:00
wanna dd905a2bed fix🐛: reset default logger fields 2024-08-23 16:49:21 +08:00
zhanluxianshen 2d76430f89 clean err define in methods.
Signed-off-by: zhanluxianshen <zhanluxianshen@163.com>
2024-07-10 15:03:33 +08:00
zhanluxianshen 5dde1d2a00 replace basemodel by common.model
Signed-off-by: zhanluxianshen <zhanluxianshen@163.com>
2024-07-10 11:26:52 +08:00
lwnmengjing 93f25c6cdf Add mss-boot-io link 2023-11-07 23:28:52 +08:00
wenjianzhang d366df372d feat✨: Log file size control and retention days control 2023-11-03 18:59:20 +08:00
wenjianzhang 7281d05efc fix🐛: Fixed system startup Network output problem 2023-11-03 17:42:01 +08:00
wenjianzhang e6d6a65267 Merge remote-tracking branch 'origin/master' 2023-11-03 17:36:21 +08:00
wenjianzhang 9ff094b6f5 fix🐛: Fixed data migration issue during multi-tenant configuration 2023-11-03 17:36:04 +08:00
wenjianzhang fc9c253a9f tag📌: Upgrade go1.21 2023-11-03 17:35:01 +08:00
wenjianzhang 9d735ed5aa docs📝: Update README.md 2023-11-02 17:42:40 +08:00
wenjianzhang d782b00117 tag📌: Change version 2023-11-02 17:10:52 +08:00
wenjianzhang 239159dd2a fix🐛: Fix the problem that el-popconfirm does not take effect 2023-11-02 17:08:58 +08:00
wenjianzhang 0c1e91c3b5 Add files via upload 2023-10-11 21:25:01 -05:00
wenjianzhang c09347b387 Merge pull request #768 from majiayu000/fix-pgerror
[BugFix] 修复一个``引发的bug
2023-10-11 21:18:42 -05:00
lif e49e47c7a1 Delete go.mod 2023-09-22 14:02:38 +08:00
wenjianzhang 98b46535aa Merge pull request #767 from zgxme/fix-gen-0909
[fix](gen) ignore default time type columns in table
2023-09-21 22:00:57 +08:00
lif 014a23aac3 [BugFix] Fix pgsql error with 2023-09-14 16:35:41 +08:00
zgxme f1dfba79e0 [fix](gen) ignore default time type columns in table 2023-09-09 22:59:45 +08:00
wenjianzhang a282e44b1d Merge pull request #753 from Vingurzhou/master
-installsuffix 参数没有指定后缀字符串。它被设定为空,这可能导致一些问题
2023-08-02 09:24:48 +08:00
wenjianzhang d1279e67fb Merge pull request #757 from NipGeihou/master
fix: 修复go generate命令不更新Swagger文档问题
2023-08-02 09:22:25 +08:00
wenjianzhang 37a5963cd6 perf👌: Optimize go warnings 2023-08-01 22:38:41 +08:00
wenjianzhang 69df1b3d34 perf👌: Remove unused attributes 2023-08-01 22:12:18 +08:00
wenjianzhang eae97f7a15 perf👌: update version 2023-08-01 22:08:47 +08:00
wenjianzhang ce0b5ff7bf perf👌: update version 2023-08-01 22:08:43 +08:00
NipGeihou 73118e49b9 fix: 修复go generate命令不更新Swagger文档问题
修复go generate不更新Swagger文档问题,并更新生成后文档文件
2023-06-20 00:37:59 +08:00
Vingurzhou 7b43982595 Update Makefile
fix(makefile): -installsuffix 参数没有指定后缀字符串。它被设定为空,这可能导致一些问题
2023-06-10 15:57:18 +08:00
wenjianzhang 31cd1ee768 Merge pull request #741 from wwhai/patch-2
fix: change 'os.Signal' channel to buffered
2023-05-14 11:04:04 +08:00
wenjianzhang 8df7551946 Merge pull request #740 from llussy/patch
fix setting.yml spelling
2023-05-14 11:03:47 +08:00
wenjianzhang 79c1295a70 Merge pull request #749 from sincatter/master_fix_pg_migrate
处理postgres迁移时insert提示类型不匹配问题
2023-05-14 11:03:07 +08:00
wenjianzhang 26d9a2e9e4 Merge pull request #747 from go-admin-team/dependabot/go_modules/golang.org/x/net-0.7.0
build(deps): bump golang.org/x/net from 0.0.0-20220722155237-a158d28d115b to 0.7.0
2023-05-13 15:38:46 +08:00
wenjianzhang b09b7b6ccc Merge pull request #729 from go-admin-team/dependabot/go_modules/github.com/prometheus/client_golang-1.11.1
build(deps): bump github.com/prometheus/client_golang from 1.11.0 to 1.11.1
2023-05-13 15:38:13 +08:00
dependabot[bot] ef6fdaa221 build(deps): bump golang.org/x/net
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220722155237-a158d28d115b to 0.7.0.
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-13 07:38:07 +00:00
wenjianzhang 23c80f0217 Merge pull request #735 from go-admin-team/dependabot/go_modules/golang.org/x/text-0.3.8
build(deps): bump golang.org/x/text from 0.3.7 to 0.3.8
2023-05-13 15:37:14 +08:00
wenjianzhang 95dc699e2e Merge pull request #746 from haimait/fix_edit_master_role模块添加注释
fix_edit_master_role模块添加注释
2023-05-13 15:36:29 +08:00
wenjianzhang 74b7d62c75 处理postgres迁移时insert提示类型不匹配问题 2023-05-13 00:00:35 +08:00
wanghaima 68accd5448 fix_edit_master_role模块添加注释 2023-05-07 19:33:00 +08:00
wenjianzhang 3edbef8696 Merge pull request #745 from haimait/fix_edit_master_优化api筛选
优化API管理筛选
2023-05-07 19:22:23 +08:00
wanghaima 5527f6386a 优化API管理筛选 2023-05-07 18:47:36 +08:00
wwhai c48f70a7c6 fix: change 'os.Signal' channel to buffered 2023-05-04 23:25:43 +08:00
llussy a078e31664 fix setting.yml 2023-04-26 15:59:19 +08:00
wenjianzhang 04d2d7dde1 format🥚: Exclude empty permission identification 2023-04-19 18:50:35 +08:00
Akiraka b846053bea 恢复 common/middleware/demo.go 2023-04-14 19:51:48 +08:00
Akiraka a1a5634c4e 恢复修改 2023-04-14 19:51:22 +08:00
Akiraka 6036c6e4e3 接受参数位置错误 2023-04-14 19:07:01 +08:00
wenjianzhang 7d74e6f325 Merge pull request #732 from wenyoufu/master
【bug】修复普通用户只用查询权限时,无法修改个人信息(昵称、用户密码)的bug
2023-03-14 01:14:43 +08:00
dependabot[bot] 1bf9f74bdf build(deps): bump golang.org/x/text from 0.3.7 to 0.3.8
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-23 00:08:28 +00:00
ford f61de5beeb 【bug】修复普通用户只用查询权限时,无法修改个人信息(昵称、用户密码)的bug 2023-02-17 19:24:50 +08:00
dependabot[bot] 79fb2d0bee build(deps): bump github.com/prometheus/client_golang
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-15 01:38:52 +00:00
wenjianzhang c973d6819c docs📝: update readme 2022-12-13 11:47:04 +08:00
wenjianzhang 3d8b879e64 docs📝: update readme 2022-12-13 11:45:28 +08:00
wenjianzhang ac971bda4b fix🐛: 忽略pkg包 2022-12-08 18:00:33 +08:00
wenjianzhang 44f62abbca fix🐛: 添加引用 2022-11-16 16:47:32 +08:00
wenjianzhang 7d7d8484d4 fix🐛: demo中间件添加环境判断 2022-11-16 12:05:33 +08:00
zhangwenjian c8b27492eb fix🐛: update sqlite3 configuration 2022-11-09 17:36:04 +08:00
zhangwenjian 783f79dcb6 Merge remote-tracking branch 'origin/master' 2022-11-09 17:35:31 +08:00
zhangwenjian ddd97d5a9e fix🐛: Adjust the demo environment configuration 2022-11-09 17:35:16 +08:00
wenjianzhang fa73c3d6b1 patch🚑: update restart 2022-11-03 14:55:02 +08:00
wenjianzhang 5b2f3e9316 Merge pull request #720 from ruishawn/dev
Docs: update README
2022-11-03 14:03:35 +08:00
zhangwenjian 42f3025217 fix🐛: Fix the problem that api saving fails when creating a new menu 2022-11-03 14:00:48 +08:00
wenjianzhang 5df43b4d11 docs📝: update readme 2022-11-02 10:18:32 +08:00
wenjianzhang eac1bf197e docs📝: update readme 2022-11-01 19:16:01 +08:00
wenjianzhang 5c834939af docs📝: update readme 2022-11-01 19:13:37 +08:00
zhangwenjian 642e86951b Merge branch 'master' of github.com:go-admin-team/go-admin 2022-11-01 16:58:11 +08:00
zhangwenjian bc42412e92 perf👌: 更新logo 2022-11-01 16:57:28 +08:00
wenjianzhang d9122d29cb docs📝: update readme 2022-11-01 16:54:19 +08:00
wenjianzhang b532ad994c docs📝: update readme 2022-11-01 16:53:42 +08:00
zhangwenjian b62fbc803c perf👌: 更新演示环境数据库名称 2022-11-01 15:02:36 +08:00
zhangwenjian ed8bac8a1b perf👌: 更新ci脚本 2022-11-01 15:00:41 +08:00
zhangwenjian d56142463a perf👌: 添加演示环境配置项 2022-11-01 14:58:18 +08:00
zhangwenjian f795a356e7 perf👌: 更新CI脚本中的分支 2022-11-01 14:14:27 +08:00
zhangwenjian a359c25e36 perf👌: 添加CI脚本 2022-11-01 14:13:53 +08:00
xiaobo 0ef422d309 fix: update README
Update README file: update dependencies before build.
2022-10-27 16:12:50 +08:00
wenjianzhang e0519a4d7e docs📝: update antd view url 2022-10-26 23:38:18 +08:00
wenjianzhang 5043ce5411 docs📝: update antd view url 2022-10-26 23:37:16 +08:00
wenjianzhang cc0cdc7d0e Merge pull request #710 from zyd/master
fix🐛:e.Log.Errorf("db error:%s", err)输出的err没有被赋值
2022-10-08 15:00:33 +08:00
zhaodongdong c004b3d333 fix🐛:e.Log.Errorf("db error:%s", err)输出的err没有被赋值 2022-09-14 17:42:09 +08:00
wenjianzhang 453dd65cb1 docs📝: update readme zh 2022-09-14 12:24:02 +08:00
wenjianzhang b55cbe1992 docs📝: Update readme 2022-09-14 12:23:06 +08:00
wenjianzhang b6e1b7b210 Merge pull request #708 from zyd/master
fix🐛:模板Update方法,err没有被赋值,返回的err永远是nil
2022-09-12 11:50:41 +08:00
zhaoyidong 3685510d25 fix🐛:模板Update方法,err没有被赋值,返回的err永远是nil 2022-09-07 20:08:17 +08:00
wenjianzhang d7e685536c Merge pull request #707 from zyd/master
fix🐛:排序参数必须用string接收
2022-09-07 09:53:24 +08:00
zhaoyidong 4df859a0ea fix🐛:排序参数必须用string接收
优化了代码生成的格式,最后的空行无法删除,删除之后}前面会增加空格
2022-09-06 14:10:27 +08:00
wenjianzhang 7d1b84e837 Merge pull request #706 from haimait/master-test
1. 修复日志创建时间筛选报错的bug.
2022-09-05 19:58:06 +08:00
wenjianzhang be1f0be9b2 Merge pull request #703 from quanbisen/master
修复优雅重启不生效
2022-09-05 19:56:46 +08:00
wanghaima 0f742900f5 1. 修复日志创建时间筛选报错的bug.
2. 修复日志里操作人,修改人,userAgent为空的bug.
3. 迁移表时,日志表添加字段注释.
4. sys_opera_log表oper_param字段类型改为text,解决字段长度报错的问题
2022-09-04 10:02:26 +08:00
quanbisen e6f4fac859 修复优雅重启不生效 2022-08-31 18:14:33 +08:00
wenjianzhang d39faa1aca docs📝: update readme zh 2022-08-25 14:25:07 +08:00
wenjianzhang c3fe13d9dd docs📝: Update readme 2022-08-25 14:24:05 +08:00
wenjianzhang 90db381b5a fix🐛: 修复侧边栏菜单排序问题 (690) 2022-08-25 14:18:02 +08:00
wenjianzhang 508137da4b docs📝: update README.md 2022-08-25 13:56:14 +08:00
wenjianzhang 695ac7b29b docs📝: Update README.Zh-cn.md 2022-08-25 13:55:11 +08:00
wenjianzhang c06df13a75 docs📝: update readme zh 2022-08-25 13:44:59 +08:00
wenjianzhang e663de3697 docs📝: update readme 2022-08-25 13:44:24 +08:00
wenjianzhang 10b4f03ff5 Merge pull request #701 from NaturalGao/natural
feat ✨:update swag && add swag commond ssh
2022-08-25 13:42:42 +08:00
wenjianzhang c7a4434a0e docs📝: update readme 2022-08-25 13:40:24 +08:00
NaturalGao 1f8babd9e7 fix: fix sys_router && add swag commond 2022-08-25 01:19:55 +08:00
NaturalGao 8e8fe906fd perf: update swag 2022-08-25 01:00:57 +08:00
wenjianzhang 386d08a03f feat✨: update issue-labeled.yml 2022-08-24 16:27:19 +08:00
wenjianzhang af38e1694b feat✨: pr_cn.md 2022-08-24 11:26:01 +08:00
wenjianzhang 5609e004cc feat✨: PULL_REQUEST_TEMPLATE.md 2022-08-24 11:24:53 +08:00
wenjianzhang 5be468308a feat✨: update issue-labeled.yml 2022-08-23 11:55:03 +08:00
wenjianzhang 39702abfba feat✨: add issue-labeled.yml 2022-08-23 11:48:35 +08:00
wenjianzhang a97d86e801 feat✨: add issue-check-inactive.yml 2022-08-23 11:40:29 +08:00
wenjianzhang 75582539fe feat✨: add issue-close-require.yml 2022-08-23 11:39:19 +08:00
wenjianzhang 466723c55e Merge pull request #606 from npmmirror/master
Update https://registry.npm.taobao.org to https://registry.npmmirror.com
2022-08-23 11:30:20 +08:00
wenjianzhang 6b3b2125df docs📝: update README 2022-08-22 15:52:19 +08:00
wenjianzhang 8c8d268708 docs📝 update README 2022-08-22 15:51:33 +08:00
wenjianzhang 73ac7273a0 Merge pull request #695 from zyd/master
fix🐛:去除模板中的多余空格
2022-08-22 15:23:17 +08:00
wenjianzhang dcafcdc6ce Merge pull request #694 from infnan/master
处理postgre启动报错问题
2022-08-22 15:22:49 +08:00
zhaoyidong 5b1405391e fix🐛:去除模板中的多余空格 2022-08-18 18:36:50 +08:00
infnan 0993173b1f 处理postgre启动报错问题
Signed-off-by: infnan <38274826+infnan@users.noreply.github.com>
2022-08-18 16:50:54 +08:00
wenjianzhang 483ec2bf3e Create config.yml 2022-08-18 13:02:42 +08:00
wenjianzhang 60fe272ba0 refactor🎨: catch exception return error message
捕获runtime.Error异常,否则接口报错不返回任何信息
2022-08-18 10:14:12 +08:00
zhaoyidong 8df2e8190e 捕获runtime.Error异常,否则接口报错不返回任何信息
报错细节不应该隐藏,方便debug。500错误应该由前端统一处理,返回用户可读信息。
2022-08-18 10:02:00 +08:00
zhangwenjian 66c8eb5ee8 fix🐛: Fix create when creating a new create_by Problem with by value of 0 (#688) 2022-08-18 07:26:56 +08:00
zhangwenjian bb65e76219 fix🐛: Repair role creation prompt empty slice found (#687) 2022-08-18 07:19:24 +08:00
zhangwenjian a585e29073 Merge remote-tracking branch 'origin/master' 2022-08-18 06:57:20 +08:00
zhangwenjian 7655d0fd38 fix🐛: Repair document address (#692) 2022-08-18 06:57:02 +08:00
wenjianzhang 5bec640f20 fix🐛: Merge pull request #689 from zyd/master
修复模板get update delete错误
2022-08-16 11:58:33 +08:00
zhaoyidong 62d9084ee8 修复模板get update delete错误 2022-08-16 11:41:28 +08:00
wenjianzhang df8ab39aa0 config🔧: Merge pull request #685 from zyd/master
删除模板中间件重复初始化代码
2022-08-15 11:15:22 +08:00
wenjianzhang a7d3666811 config🔧: Merge pull request #686 from haimait/master_dev
编写dockerfile启动脚本
2022-08-15 11:15:07 +08:00
wanghaima ea9e3d2fe1 编写dockerfile启动脚本
编辑shell启动脚本
2022-08-14 23:32:58 +08:00
zhaoyidong f14085f3ee Merge pull request #1 from zyd/zyd-patch-1
删除模板中间件重复初始化代码
2022-08-13 17:39:14 +08:00
zhaoyidong e8b9db1df5 删除模板中间件重复初始化代码
go-admin app -n 创建目录,重复初始化会导致获取不到body中的参数
2022-08-13 17:37:55 +08:00
wenjianzhang dc625997c4 docs📝: Update README.Zh-cn.md 2022-08-10 01:00:34 +08:00
wenjianzhang 4f458591e8 docs📝: Update README.md 2022-08-10 00:47:42 +08:00
zhangwenjian 7a074d93cd config🔧: Modify the system default logo URL 2022-08-10 00:18:15 +08:00
wenjianzhang 9d1e1f6482 docs📝: Update README.Zh-cn.md 2022-08-09 23:24:35 +08:00
wenjianzhang 19153170bb docs📝: Update README.md 2022-08-09 23:23:18 +08:00
wenjianzhang 6bf774c463 fix🐛: fix rolemenu
fix🐛: fix rolemenu
2022-08-09 23:14:56 +08:00
wenjianzhang 0de5ba77aa docs📝: Update README.Zh-cn.md 2022-08-09 21:22:27 +08:00
wenjianzhang f4c0134d9c docs📝: Update Readme.md 2022-08-09 21:20:49 +08:00
wenjianzhang 2c5c1b69b4 docs📝: update readme 2022-08-09 20:46:37 +08:00
zhangwenjian d34a33b691 perf👌: update sqlite3 file 2022-08-09 18:21:40 +08:00
zhangwenjian 869394c898 perf👌: Remove caspin table from data migration 2022-08-09 18:21:00 +08:00
zhangwenjian b97bde11b2 perf👌: upgrade gorm,casbin,gin,uuid version 2022-08-09 18:19:02 +08:00
zhangwenjian 852cfa66e8 perf👌: remove casbin sys_ 2022-08-09 18:17:42 +08:00
zhangwenjian 289fbba8e0 perf👌: update casbin gorm adapter 2022-08-09 18:17:06 +08:00
zhangwenjian a6ffac657e fix🐛: Add MySQL judgment in data migration 2022-08-09 15:20:45 +08:00
zhangzhenlun 096663ac91 fix🐛: fix rolemenu 2022-08-09 14:26:43 +08:00
zhangwenjian d40a0c0837 fix🐛: fix github.com/alibaba/sentinel-golang middleware(#679) 2022-08-09 13:00:32 +08:00
zhangwenjian f94b437852 fix🐛: fix github.com/alibaba/sentinel-golang middleware 2022-08-09 12:59:31 +08:00
wenjianzhang 7e571b038f fix🐛: fix rolemenu 2022-08-09 10:18:21 +08:00
zhangwenjian 56968c0bd2 Merge remote-tracking branch 'origin/master' 2022-08-08 18:03:04 +08:00
zhangwenjian ef6b85faec config🔧: Modify the instruction createapp to app 2022-08-08 18:02:50 +08:00
zhangwenjian 0d47fb4e68 Merge branch 'master' of github.com:go-admin-team/go-admin 2022-08-08 16:28:34 +08:00
zhangwenjian 8bfee8af16 refactor🎨: 添加菜单paths默认数据 2022-08-08 16:28:26 +08:00
wenjianzhang 0fc7276ccd refactor🎨: set DB CHARSET utf8mb4 2022-08-08 11:39:11 +08:00
Vingurzhou 9a596397ca Update 1599190683659_tables.go 2022-08-08 11:28:55 +08:00
zhangwenjian 1bea64bb6d config🔧: set DB CHARSET utf8mb4(#674) 2022-08-08 10:16:43 +08:00
zhangwenjian 09bfdc3d39 config🔧: set DB CHARSET utf8mb4 2022-08-08 10:15:29 +08:00
wenjianzhang fe8b39691b patch🚑: go1.18 2022-08-08 10:00:24 +08:00
wenjianzhang 1465bfdf15 Merge branch 'master' into dev1.18 2022-08-08 09:59:57 +08:00
zhangwenjian bccbd67450 config🔧: update README 2022-08-08 09:42:31 +08:00
zhangwenjian 50a3b39666 config🔧: Modify the client IP acquisition method 2022-08-08 09:26:14 +08:00
zhangwenjian 087ba38c24 config🔧: update README.md 2022-08-08 09:22:36 +08:00
zhangwenjian 8a3c50ea1a config🔧: 修改actions配置文件 2022-08-07 21:52:02 +08:00
zhangwenjian 86187e4c79 fix🐛: 修复获取菜单接口menurole,数据不完整 (#676) 2022-08-07 21:49:24 +08:00
zhangwenjian 98c495abb1 refactor🎨: update readme 2022-08-07 20:43:34 +08:00
zhangwenjian c53c4fd9f8 refactor🎨: update readme 2022-08-07 20:42:24 +08:00
zhangwenjian b231705a67 Merge remote-tracking branch 'origin/master' 2022-08-04 17:25:17 +08:00
zhangwenjian 86f94a2cb9 refactor🎨: errors 添加go mod 2022-08-04 17:24:53 +08:00
zhangwenjian ef41e07550 docs📝: 添加开发环境要求 2022-08-04 17:24:07 +08:00
zhangwenjian 255c72d3f1 refactor🎨: update version 2022-07-29 18:51:55 +08:00
zhangwenjian 260eedfcc6 refactor🎨: 移除失效文档链接 2022-07-29 18:50:46 +08:00
zhangwenjian f43cd117e3 refactor🎨: 角色创建和更新后重新load policy策略 2022-07-29 18:47:12 +08:00
zhangwenjian 83b219458f fix🐛: 菜单中paths未设置问题修复 2022-07-29 18:44:22 +08:00
zhangwenjian 0f1b9369df fix🐛: 自定义错误中间件bug修复 2022-07-29 18:43:38 +08:00
zhangwenjian b31e1c0d58 feat✨: 升级go1.18 2022-07-27 22:27:38 +08:00
zhangwenjian 0122024789 feat✨: 修改版本号 2022-07-27 21:55:59 +08:00
zhangwenjian f469536174 fix🐛: 添加bcrypt包的引用 2022-07-27 21:49:24 +08:00
wenjianzhang 70bd8b26ad Merge pull request #673 from go-admin-team/dev
Dev
2022-07-27 21:36:11 +08:00
zhangwenjian dabc4d88b3 Merge remote-tracking branch 'origin/master' 2022-07-27 21:34:07 +08:00
wenjianzhang 214f90b366 Merge pull request #661 from wxxiong6/patch-1
fix UpdatePwd error
2022-07-27 21:20:07 +08:00
wenjianzhang 3c4cc054df Merge pull request #671 from Silicon-He/fix-readme-cgo-url
Fix readme cgo url
2022-07-27 21:19:29 +08:00
wenjianzhang 4d329287ce Merge pull request #664 from zhouxixi-dev/dev
bugfix: https://github.com/go-admin-team/go-admin/issues/539
2022-07-27 21:17:43 +08:00
siliconhe 866714d75e update doc url of cgo-issue 2022-07-17 21:14:26 +08:00
lwnmengjing b268d03e30 💚 update workflow 2022-07-12 11:54:24 +08:00
lwnmengjing 953d3b4135 🐛 fix: delete pkg error package 2022-07-12 10:55:05 +08:00
zhouxixi-dev 0d6b347d7e bugfix: https://github.com/go-admin-team/go-admin/issues/539 修复角色新增、修改时,sys_casbin_rule表drop,然后重新create的问题 2022-06-23 15:47:55 +08:00
wxxiong6 a19622f6ac fix UpdatePwd error
fix UpdatePwd error
2022-06-13 23:36:12 +08:00
zhangwenjian 45c8737601 refactor🎨: 引入github.com/pkg/errors 2022-06-05 11:12:19 +08:00
wenjianzhang 4925383f5e Merge pull request #593 from ziux/ziux
fix bug
2022-06-05 11:08:09 +08:00
wenjianzhang 62ab985050 Merge pull request #552 from wkf928592/wkf928592-patch-1
fix: arm32位系统环境下使用migrate迁移功能时,版本号作为整型处理会出现内存溢出的问题
2022-06-05 10:36:27 +08:00
wenjianzhang 50f14f7658 Merge branch 'dev' into wkf928592-patch-1 2022-06-05 10:36:20 +08:00
wenjianzhang 1cd31079d7 Merge pull request #634 from defool/bugfix/throw_error_on_migrate
Throw error if migrate failed
2022-06-05 10:33:08 +08:00
wenjianzhang 46b62a9809 Merge pull request #655 from stephenzhang0713/dev
Fix: Unable to load config file to Docker container
2022-06-05 10:31:54 +08:00
Han Zhang 8f70a1dca4 fix🐛: Fix Dockerfile to load config file
fix🐛: Fix Dockerfile to load config file
2022-06-03 20:38:54 +08:00
zhangwenjian 937b62e641 添加errors包 2022-05-29 12:58:53 +08:00
wenjianzhang 3873342f6c Merge pull request #650 from go-admin-team/dev
refactor🎨: 修复问题
2022-05-28 00:40:06 +08:00
zhangwenjian 717903a2b6 fix🐛: 修复关闭中的job能够启动问题(#638) 2022-05-28 00:34:08 +08:00
zhangwenjian 88030e301a patch🚑: 更新版本信息 2022-05-28 00:17:35 +08:00
zhangwenjian cd0792e3d2 refactor🎨: 更新readme(#623) 2022-05-28 00:14:12 +08:00
zhangwenjian 6ed2fcbf6c fix🐛: 添加roleKey验证(#649) 2022-05-27 22:47:57 +08:00
kaiyuan eb33515e29 throw error if migrate failed 2022-04-08 10:32:07 +08:00
wenjianzhang bf93b86bd0 Merge pull request #629 from go-admin-team/dev
docs📝: update readme
2022-04-01 14:14:01 +08:00
wenjianzhang c41672c21a docs📝: update readme 2022-03-31 15:08:49 +08:00
wenjianzhang 8716b073df Merge pull request #628 from go-admin-team/dev
docs📝:  update readme
2022-03-31 14:13:26 +08:00
wenjianzhang 638bab3c9d docs📝: update readme 2022-03-31 14:01:15 +08:00
wenjianzhang c5d7a8c740 fix🐛: Fix password reset
fix🐛: Fix password reset
2022-03-15 11:33:21 +08:00
wenjianzhang b030be8f80 fix🐛: Fix password reset 2022-03-12 13:00:19 +08:00
wenjianzhang 1fe19d1c3b patch🚑: dev merge
patch🚑:  dev merge
2022-03-05 11:54:10 +08:00
wenjianzhang 1508e850fe Merge branch 'master' into dev 2022-03-05 11:52:44 +08:00
zhangwenjian 81bc15d77c config🔧: go-admin version info 2022-03-05 11:49:56 +08:00
zhangwenjian 24adca55e4 feat✨: added file update sdk;kodo、obs 2022-03-05 11:44:23 +08:00
wenjianzhang 9c8974a26e fix🐛: 修复前端设置数据权限不生效问题
fix🐛: 修复前端设置数据权限不生效问题
2022-03-05 11:23:41 +08:00
wenjianzhang 01e8984b79 fix🐛: Fix readme 404 link.
fix🐛:  Fix readme 404 link.
2022-03-05 11:13:08 +08:00
wenjianzhang 7f1aa89539 fix🐛: Fix the newline problem in time package in code generation
Fix gen code problem
2022-03-05 11:11:34 +08:00
zhangwenjian 4876fc0aa1 fix🐛: Fix password reset caused by modifying user information 2022-03-05 11:00:52 +08:00
zhangwenjian f998d20a86 feat✨: added obs,kodo 2022-02-21 18:07:37 +08:00
zhangwenjian cdb5faf043 refactor🎨: upgrade OXS interface 2022-02-21 18:06:11 +08:00
zhangwenjian dfaa2ff51e test✅: added oss test 2022-02-21 18:04:46 +08:00
zhangwenjian 8754ff8147 refactor🎨: upgrade oss 2022-02-21 18:04:18 +08:00
zhangwenjian cca84c3c21 docs📝: update License Copyright 2022-02-21 17:56:12 +08:00
wenjianzhang 72391f0201 Merge pull request #598 from go-admin-team/dev
fix🐛: fix monitor macos env error (#605)
2022-02-13 00:54:52 +08:00
zhangwenjian 39e26d738c docs📝: update version 2.0.9 2022-02-13 00:31:46 +08:00
zhangwenjian f114424079 fix🐛: fix monitor macos env error 2022-02-13 00:23:20 +08:00
NPM Mirror Bot 98b60f0564 update https://registry.npm.taobao.org to https://registry.npmmirror.com 2022-02-12 05:56:42 +00:00
wenjianzhang d02b52f383 feat✨: 添加sqlserver支持 2022-02-08 18:41:09 +08:00
horizonzy b52da434bb fix code gen problem. 2022-01-31 13:00:21 +08:00
horizonzy 3cfa7a2767 fix code gen problem. 2022-01-31 12:00:26 +08:00
horizonzy 054199d1e4 fix 404 link. 2022-01-30 17:19:50 +08:00
zhangwenjian 02b62a288d refactor🎨: 删除历史的sqlite文件 2022-01-22 23:05:38 +08:00
zhangwenjian 24908a8732 refactor🎨: 添加error判断返回 2022-01-22 23:03:01 +08:00
wenjianzhang 26ee7b7985 refactor🎨: 清空sqlite数据库文件 2022-01-22 21:49:42 +08:00
wenjianzhang b76db48112 refactor🎨: 修正针对sqlite3的事务问题 2022-01-22 21:49:11 +08:00
wenjianzhang e2c5075319 v2.0.8
1、修改sqplite的支持
2、修正已知问题
2022-01-22 20:03:21 +08:00
wenjianzhang e9f36e74ca refactor🎨: 修改版本号 2022-01-22 19:44:36 +08:00
wenjianzhang 0b73bd7b25 refactor🎨: 修改sqplite的支持 2022-01-22 19:37:56 +08:00
yangyu ccccab3104 fix bug 2022-01-12 17:10:52 +08:00
wenjianzhang ae8e32d806 Update README.Zh-cn.md 2022-01-10 13:18:59 +08:00
wenjianzhang fd2709affa Update README.md 2022-01-10 13:18:23 +08:00
wenjianzhang 34b3395d15 Update README.md 2022-01-10 13:17:28 +08:00
inits abdc80b756 修复前端设置数据权限不生效问题 2022-01-10 10:21:10 +08:00
lwnmengjing 492ac31973 Merge pull request #580 from go-admin-team/dev
push docker
2021-12-08 11:09:01 +08:00
linwenxiang 3ac1878c3c perf ⚡ performance docker build 2021-12-07 23:37:33 +08:00
linwenxiang 0cc27355f7 fix 🐛 push to gihub 2021-12-07 23:24:19 +08:00
linwenxiang 562f761807 feat ✨ add dev to ci 2021-12-07 22:47:44 +08:00
linwenxiang 88e4b37c03 feat ✨ push docker to github 2021-12-07 22:44:39 +08:00
wenjianzhang b73d88d6cf Merge pull request #564 from go-admin-team/dev
merge: 修正数据初始化的部分问题
2021-10-22 12:15:05 +08:00
wenjianzhang 443c30d48c Update 1599190683659_tables.go 2021-10-22 12:05:39 +08:00
wenjianzhang 64cbf31184 Update db.sql 2021-10-22 12:04:48 +08:00
wkf928592 ce9d9bd3ec fix:在32位系统中做迁移时,版本号作为整型处理会造成内存溢出的问题
修改版本号作为字符串处理
2021-09-10 10:19:33 +08:00
linwenxiang 57330784ac feat ✨ mirror to gitlab 2021-09-07 21:45:25 +08:00
lwnmengjing 85e1c6fe54 Merge branch 'dev' 2021-09-07 11:21:33 +08:00
lwnmengjing 7ca776bfab 💚 修复流水线CI bug 2021-09-07 11:20:55 +08:00
linwenxiang 082c369d41 feat ✨ 同步代码到gitee 2021-09-06 21:51:07 +08:00
lwnmengjing 0ac9f41e1a Merge pull request #549 from go-admin-team/dev
fix 🐛 gcc强依赖问题修复
2021-09-02 20:38:36 +08:00
linwenxiang 16701d38a6 feat ✨ 增加release pipeline 2021-09-02 20:31:00 +08:00
linwenxiang 30eb280698 fix 🐛 gcc强依赖问题修复 2021-09-02 20:21:18 +08:00
wenjianzhang 974a8096ca Merge pull request #546 from go-admin-team/dev
Dev
2021-08-21 14:20:26 +08:00
wenjianzhang bb83a97613 refactor🎨: 修改post接口文档 2021-08-20 18:28:04 +08:00
wenjianzhang 8baae5e712 fix🐛: 修复jwt密钥引用错误问题(#545) 2021-08-20 18:27:29 +08:00
wenjianzhang 49e4c19cbb Merge pull request #544 from go-admin-team/dev
Dev
2021-08-19 19:36:42 +08:00
wenjianzhang 6f67628012 Merge branch 'dev' of github.com:go-admin-team/go-admin into dev 2021-08-19 19:28:04 +08:00
wenjianzhang 8a1573cc14 docs📝: 更新2.0.6 2021-08-19 19:27:55 +08:00
wenjianzhang 03b916ef8a Merge pull request #543 from go-admin-team/dev
Dev
2021-08-19 19:26:31 +08:00
wenjianzhang 9d25648c1f Merge pull request #540 from ninstein/patch-8
BUGFIX:角色状态修改异常修复
2021-08-19 19:13:16 +08:00
wenjianzhang aa6c3df892 Merge pull request #542 from go-admin-team/dev
Dev
2021-08-19 19:12:32 +08:00
wenjianzhang bdaa6e0db0 refactor🎨: 升级包go-admin-core v1.3.7和go-admin-core/sdk v1.3.7至v1.3.8 2021-08-19 19:10:32 +08:00
wenjianzhang 4740a39808 refactor🎨: 删除移除功能的数据初始化 2021-08-19 19:08:47 +08:00
wenjianzhang 386c620b48 refactor🎨: 优化角色修改时循环AddNamedPolicy 2021-08-19 19:04:03 +08:00
wenjianzhang 2441412714 fix🐛: 修复参数验证信息 2021-08-19 19:03:19 +08:00
ninstein 9db940150a BUGFIX:角色状态修改异常修复
切换角色状态时参数传递丢失,导致切换异常新增了一条空记录
2021-08-18 14:45:22 +08:00
wenjianzhang 8c5639af53 Merge pull request #535 from go-admin-team/dev
Dev
2021-08-13 21:17:00 +08:00
wenjianzhang b4a6f82f5f Merge pull request #534 from appleboy/patch
chore: upgrade gin to v1.7.3
2021-08-13 21:15:58 +08:00
Bo-Yi Wu 7dd62a4cf8 chore: upgrade gin to v1.7.3
Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2021-08-13 20:53:37 +08:00
wenjianzhang 095ed7c2fd Merge pull request #529 from go-admin-team/dev
Dev
2021-08-10 16:39:54 +08:00
zhangwenjian 76567eea84 docs📝: 更新2.0.5 2021-08-10 14:58:55 +08:00
zhangwenjian 5a65fcd477 fix🐛: 修复菜单树 2021-08-10 14:58:04 +08:00
wenjianzhang 27b0e1a07a Merge pull request #528 from go-admin-team/dev
Dev
2021-08-10 03:55:34 +08:00
zhangwenjian 3e20e93797 fix🐛: 修复菜单编辑未赋权接口列表 2021-08-10 03:47:31 +08:00
zhangwenjian 5d3b1c3d0f docs📝: 更新2.0.4 2021-08-10 03:37:02 +08:00
zhangwenjian b5a57e6dd9 refactor🎨: 优化生成功能的修改和删除询问提示 2021-08-10 03:36:20 +08:00
zhangwenjian 2decf43b4c fix🐛: 统一生成后的路由 2021-08-10 03:35:36 +08:00
wenjianzhang 7dd3e2b27e Merge pull request #525 from go-admin-team/dev
Dev
2021-08-06 10:49:41 +08:00
zhangwenjian 325c91989c refactor🎨: api自动添加不设置默认类型 2021-08-06 10:35:09 +08:00
wenjianzhang 96250bafb1 Merge pull request #521 from qliang/master
完善:接口检查新增记录-根据接口注释补充接口名称信息
2021-08-06 10:28:07 +08:00
wenjianzhang fff795ce5a Merge pull request #524 from go-admin-team/dev
fix🐛: 修复代码生成字典的问题 (#523  #517)
2021-08-06 10:27:32 +08:00
zhangwenjian 9887250407 docs📝: 更新2.0.3 2021-08-06 10:03:44 +08:00
zhangwenjian e42191c6af fix🐛: 修复代码生成字典的问题 (#523 #517) 2021-08-06 10:00:17 +08:00
lq adce44dc1f 完善:接口检查新增记录-根据接口注释补充接口名称信息 2021-08-03 17:24:58 +08:00
wenjianzhang a70ee44466 Merge pull request #511 from go-admin-team/dev
patch🚑:  merge dev
2021-07-28 10:00:12 +08:00
zhangwenjian fd0fa49f1c docs📝: 更新2.0.2 2021-07-28 09:06:23 +08:00
zhangwenjian 10491f9745 fix🐛: 修复删除部门的问题 (#510) 2021-07-28 08:53:18 +08:00
zhangwenjian 981313c0e2 fix🐛: 修复创建用户时的问题 ( #506) 2021-07-28 08:45:39 +08:00
zhangwenjian 6b88c9a004 fix🐛: 更新接口文档注释 (#507) 2021-07-27 19:16:47 +08:00
wenjianzhang 25395b5006 Merge pull request #504 from go-admin-team/dev
1. 修复菜单的目录(#500)
1. 调整字段判断逻辑
2021-07-23 00:43:34 +08:00
zhangwenjian 2f516b49cf refactor🎨: 调整字段判断逻辑 2021-07-23 00:33:39 +08:00
zhangwenjian 41ff26edc8 fix🐛: 修复菜单的目录(#500) 2021-07-23 00:33:00 +08:00
wenjianzhang a1c6f586cf Merge pull request #503 from go-admin-team/dev
fix🐛: 修复createapp时的问题(#493)
2021-07-22 23:11:56 +08:00
zhangwenjian 8b01126e0f fix🐛: 修复createapp时的问题(#493) 2021-07-22 23:03:44 +08:00
wenjianzhang e59d40af21 Merge pull request #495 from go-admin-team/dev
Dev
2021-07-22 22:22:20 +08:00
wenjianzhang 6b476bfab7 Update go.mod 2021-07-18 22:29:34 +08:00
wenjianzhang 12429d4585 Merge pull request #489 from Cassuis/dev
fix:修复createapp未初始化导致无法创建app以及修改资本资料导致密码重复加密问题
2021-07-16 20:52:40 +08:00
Vincent 6fe2edbe89 fix:修复修改基本资料导致密码重复加密问题 2021-07-15 11:13:37 +08:00
Vincent cee6bd6abd fix:修复createapp未初始化导致无法创建app的问题 2021-07-15 10:21:26 +08:00
zhangwenjian 4ac3350920 refactor🎨: 部分函数名称优化 2021-07-15 00:53:43 +08:00
zhangwenjian a45113258c refactor🎨: update request mode name 2021-07-15 00:43:06 +08:00
zhangwenjian 4a2659573b docs📝: 用户接口文档 2021-07-14 22:36:03 +08:00
zhangwenjian c74080664f refactor🎨: update version 2021-07-14 22:24:26 +08:00
wenjianzhang 84e06395a5 Merge pull request #487 from go-admin-team/dev
Dev
2021-07-14 16:24:46 +08:00
zhangwenjian 411b85afcd publish🚀: 2.0.0 2021-07-14 11:49:54 +08:00
zhangwenjian 57d128144d feat✨: Add the createapp command 2021-07-14 11:48:40 +08:00
wenjianzhang a7fa7e079b Merge pull request #486 from go-admin-team/dev
Dev
2021-07-14 11:40:05 +08:00
zhangwenjian 2781e413dc refactor🎨: 修改版本号 2021-07-14 11:16:27 +08:00
zhangwenjian 41d8daac97 fix🐛: 修改用户其它信息导致密码被置空,数据权限 #484 2021-07-14 11:13:26 +08:00
zhangwenjian dd22d55ee7 refactor🎨: request name cancel 2021-07-14 09:14:44 +08:00
zhangwenjian 32a1bd2511 refactor🎨: 升级gin和gorm版本 2021-07-05 00:31:31 +08:00
zhangwenjian 1130d20f14 refactor🎨: dto》request 2021-07-05 00:03:38 +08:00
wenjianzhang 90b17e995a Merge pull request #478 from go-admin-team/dev
Dev
2021-07-04 23:33:55 +08:00
zhangwenjian 1c41b0ec72 refactor🎨: 操作log dto模型名称修改 2021-07-04 23:26:43 +08:00
zhangwenjian 3f90605589 refactor🎨: 操作log添加字符限制 2021-07-04 23:13:14 +08:00
zhangwenjian c1347fbb5d refactor🎨: 升级依赖关系 2021-07-04 23:12:21 +08:00
zhangwenjian 0d1cb2ee33 docs📝: 升级qq群至2000人 2021-07-04 23:11:58 +08:00
zhangwenjian b6be297a1e refactor🎨: 添加默认demo代码生成表 2021-07-04 13:43:32 +08:00
zhangwenjian b9b3cbee93 refactor🎨: 调整登陆日志和api和操作日志模块 2021-07-04 13:43:11 +08:00
zhangwenjian 37d318b4d9 feat✨: vue-cli@3 升级为 vue-cli@4、Change Node Sass to Dart Sass、代码生成工具
1. vue-cli@3 升级为 vue-cli@4
2. Change Node Sass to Dart Sass
3. 代码生成工具
2021-07-04 05:46:20 +08:00
wenjianzhang 5368cfbcb8 Merge pull request #475 from G-Akiraka/patch-4
增加磁盘列表主机名称与当前时间
2021-07-02 22:26:09 +08:00
wenjianzhang 72a4ba077c Merge pull request #472 from G-Akiraka/patch-1
Update settings.yml
2021-07-02 22:25:53 +08:00
wenjianzhang fba03625d0 Merge pull request #473 from G-Akiraka/patch-2
翻译错误,管理员管理应该是用户管理
2021-07-02 22:24:25 +08:00
G-Akiraka 007807e776 增加磁盘列表主机名称与当前时间
上一个pr提交作废
2021-07-02 10:06:48 +08:00
G-Akiraka f2ae95d932 翻译错误,管理员管理应该是用户管理 2021-07-02 09:09:20 +08:00
G-Akiraka 25d7323ed1 Update settings.yml 2021-07-02 09:05:25 +08:00
wenjianzhang f7e737534d Merge pull request #471 from go-admin-team/dev
Dev
2021-07-01 23:13:03 +08:00
zhangwenjian 760c6b2814 refactor🎨: 修改版本号 2021-07-01 22:25:43 +08:00
zhangwenjian d0f49ef8c7 refactor🎨: 修改错误信息提示(#165) 2021-07-01 22:24:32 +08:00
wenjianzhang 1295b1fd35 Merge pull request #469 from go-admin-team/dev
DEV (#468)
2021-06-30 22:48:45 +08:00
zhangwenjian eb8062d19b refactor🎨: 生成逻辑调整 2021-06-30 22:36:26 +08:00
zhangwenjian d82129d364 refactor🎨: 代码生成模版升级 2021-06-30 22:35:49 +08:00
zhangwenjian 16923017b2 refactor🎨: 配置文件默认使用memory cache 2021-06-30 22:35:29 +08:00
zhangwenjian f4396e7e83 fix🐛: 用户修改头像接口入参模型分离(#468) 2021-06-30 18:55:16 +08:00
wenjianzhang acee466fc8 Merge pull request #467 from go-admin-team/dev
Dev(#457)
2021-06-30 02:04:10 +08:00
zhangwenjian 862f24d8a7 refactor🎨: 移除服务管理 2021-06-30 01:52:47 +08:00
wenjianzhang e2cb033670 refactor🎨: 增加server manager,提升服务启动流程规范
feature ✨ 增加server manager,提升服务启动流程规范
2021-06-29 16:58:50 +08:00
zhangwenjian 029c505501 refactor🎨: 修改包引用 2021-06-29 16:46:00 +08:00
zhangwenjian dd5f0c52fb refactor🎨: 修改错误信息提示 2021-06-29 16:33:03 +08:00
zhangwenjian f80e64688e refactor🎨: 修改请求参数命名 2021-06-29 16:32:44 +08:00
zhangwenjian 3fd34db3c1 docs📝: 修改接口文档 2021-06-29 16:30:47 +08:00
zhangwenjian 0607d462d0 docs📝: 修改config接口文档 2021-06-29 16:28:08 +08:00
zhangwenjian 7f8c302405 refactor🎨: 调整index to go-admin 接口 2021-06-29 16:27:10 +08:00
zhangwenjian c82c58443c refactor🎨: 验证码文档修改,去掉token验证 2021-06-29 16:26:27 +08:00
zhangwenjian 9591be883f refactor🎨:登陆模块接口文档整理 2021-06-25 11:34:26 +08:00
zhangwenjian acdcd0c867 refactor🎨:接口文档重新生成 2021-06-25 11:33:55 +08:00
zhangwenjian 0f623521c4 refactor🎨:代码生成功能迁移 2021-06-25 11:33:41 +08:00
wenjianzhang 91b93a3f48 refactor🎨:模版升级 2021-06-24 20:21:40 +08:00
wenjianzhang 32d123eb89 refactor🎨:格式化函数名称 2021-06-24 20:18:20 +08:00
wenjianzhang 2025809d91 refactor🎨:修改Syspost模块功能 2021-06-24 20:17:58 +08:00
wenjianzhang d552875e8a Merge pull request #461 from go-admin-team/dev
Dev
2021-06-23 12:21:13 +08:00
wenjianzhang 6994857f4b Merge pull request #460 from Cassuis/dev
bugfix:修正用户修改密码put接口url错误导致的无法修改密码问题,修正初始化SQL异常导致部分表无缺省参数问题
2021-06-23 12:08:37 +08:00
Vincent 7ac941c1d0 bugfix:
1.修正用户修改密码put接口url错误导致的无法修改密码问题
2.修正初始化SQL异常导致部分表无缺省参数问题
2021-06-23 09:46:51 +08:00
wenjianzhang af68778436 Merge pull request #455 from go-admin-team/dev
docs📝:  更新swagger文档
2021-06-20 00:59:39 +08:00
zhangwenjian 1ab11c46ba docs📝: 更新swagger文档 2021-06-20 00:51:55 +08:00
wenjianzhang add355637d Merge pull request #450 from go-admin-team/dev
merge Dev
2021-06-20 00:51:05 +08:00
zhangwenjian 65380695f2 refactor🎨:格式化函数名称 2021-06-20 00:43:17 +08:00
zhangwenjian a6116ede02 refactor🎨: 修改字典类型status为int 2021-06-20 00:39:43 +08:00
zhangwenjian dafa627e31 refactor🎨:升级依赖版本 2021-06-20 00:15:54 +08:00
zhangwenjian 993115c8ae fix🐛: 修复获取部门数据查询参数问题 2021-06-20 00:15:34 +08:00
zhangwenjian b97b3bc6d8 refactor🎨:部门创建添加事务,已经修改status字段为int类型 2021-06-20 00:14:45 +08:00
zhangwenjian 636294b6f5 refactor🎨: 岗位删除修改为data传值方式 2021-06-20 00:13:17 +08:00
zhangwenjian 37fe63126c fix🐛: 修复2.0 字典类型删除接口500并没有提示消息 (#452) 2021-06-18 22:24:10 +08:00
zhangwenjian 9ace1d8201 fix🐛: 修复2.0 字典数据删除404 (#451) 2021-06-18 22:23:37 +08:00
zhangwenjian 25472887c9 refactor🎨:移除内容管理、行政区管理和资源管理 2021-06-18 21:34:57 +08:00
zhangwenjian 5e7d2614b3 refactor🎨:修改版本号 2021-06-17 22:14:07 +08:00
zhangwenjian 484246e146 refactor🎨:更新数据初始化sql 2021-06-17 22:10:10 +08:00
zhangwenjian 65fb965f66 refactor🎨:修改行政区数据sql 2021-06-17 21:46:42 +08:00
zhangwenjian 493d714723 refactor🎨:修改行政区数据sql 2021-06-17 21:46:19 +08:00
zhangwenjian a911e73238 refactor🎨:调整行政区命名 2021-06-17 21:45:37 +08:00
zhangwenjian dff133bbbc refactor🎨:注释数据权限控制历史版本方法 2021-06-17 21:11:58 +08:00
zhangwenjian 60d9c59544 refactor🎨:删除历史版本菜单、角色、角色部门关系、角色菜单关系业务 2021-06-17 21:11:09 +08:00
zhangwenjian 185df737c8 refactor🎨:调整管理员和字典相关结构体字段顺序 2021-06-17 21:10:12 +08:00
zhangwenjian a0b7d9969a Revert "refactor🎨:注释未使用的对象"
This reverts commit 3358eeb54c.
2021-06-17 11:28:51 +08:00
zhangwenjian 3358eeb54c refactor🎨:注释未使用的对象 2021-06-17 11:28:37 +08:00
wenjianzhang d32751ea5d refactor🎨:文件上传修改为本地路径 2021-06-16 18:34:51 +08:00
wenjianzhang 8f637e02d0 refactor🎨:菜单、角色、用户模块调整 2021-06-16 18:34:25 +08:00
wenjianzhang e4ffd1df14 refactor🎨:注释返回数据记录 2021-06-16 18:33:56 +08:00
wenjianzhang 869bea2163 refactor🎨:系统监控接口 2021-06-16 18:33:14 +08:00
linwenxiang 7e9919e3ae feature ✨ 增加server manager,提升服务启动流程规范 2021-06-16 10:13:26 +08:00
wenjianzhang 5afe67bd1b refactor🎨:更新角色模块 2021-06-15 19:03:37 +08:00
wenjianzhang 13f09d6059 refactor🎨:数据迁移结构体重命名 2021-06-15 19:02:57 +08:00
wenjianzhang 1c9d2075d7 refactor🎨:调整接口和行政区结构体 2021-06-15 17:54:46 +08:00
wenjianzhang 3a688c06a1 refactor🎨:初始化脚本针对关键字添加引号 2021-06-15 17:54:04 +08:00
wenjianzhang 2d4b4d617d Merge branch 'dev' 2021-06-15 12:28:02 +08:00
wenjianzhang 9fb23b45cd Merge branch 'dev' 2021-06-15 12:25:46 +08:00
wenjianzhang af7a3e99a8 efactor🎨:修改行政区域数据结构
主键不需要自增
2021-06-15 12:22:30 +08:00
wenjianzhang 992d892523 refactor🎨:补充初始化数据 2021-06-15 12:16:44 +08:00
zhangwenjian 7699d8cf42 refactor🎨: 调整行政区管理相关 2021-06-15 09:29:35 +08:00
zhangwenjian f81c9c2ca0 refactor🎨: 调整删除时获取使用函数获取id 2021-06-15 09:28:58 +08:00
zhangwenjian 76351fe685 refactor🎨: 修正结构定义方法和绑定数据类型 2021-06-15 09:28:04 +08:00
zhangwenjian f0e8f18c6d refactor🎨: 更新客户端ip获取方法 2021-06-14 20:08:30 +08:00
zhangwenjian 880a3700d1 refactor🎨: 添加日志排序 2021-06-14 20:07:48 +08:00
zhangwenjian 273f2ba8b0 fix🐛: merge 的遗漏 2021-06-13 23:20:30 +08:00
zhangwenjian a3507998f7 Merge branch 'dev' 2021-06-13 23:14:34 +08:00
zhangwenjian 67d393a222 docs📝: update readme & dockfile 2021-06-13 23:09:11 +08:00
zhangwenjian 372248c819 Merge branch 'dev' 2021-06-13 23:07:37 +08:00
zhangwenjian b837489af0 docs📝: 更新readme 2021-06-13 21:52:15 +08:00
zhangwenjian e7223be040 refactor🎨: 升级依赖 2021-06-13 21:48:51 +08:00
zhangwenjian e37743733d refactor🎨: 更新初始化数据sql 2021-06-13 21:48:03 +08:00
zhangwenjian 58a9b00120 refactor🎨: 注释演示环境代码 2021-06-13 21:29:32 +08:00
zhangwenjian d8a627f880 refactor🎨: 优化资源管理的路由函数名称 2021-06-13 21:28:14 +08:00
zhangwenjian df46f86130 fix🐛: 修复菜单更新是不能更新绑定api的问题 2021-06-13 21:27:14 +08:00
zhangwenjian c98dd649eb refactor🎨: 添加更新handle 2021-06-13 21:25:47 +08:00
zhangwenjian da15a2d3bb refactor🎨: 调整SysChinaAreaData Api的函数名称以及路由引用 2021-06-13 21:24:09 +08:00
zhangwenjian 00c87d7c80 feat✨: api rabc检测时将排除列表中的path剔除不在参与验证 2021-06-13 21:22:55 +08:00
zhangwenjian 7c8c285fb8 feat✨: 添加排除路由列表 2021-06-13 21:20:44 +08:00
zhangwenjian e0cf7af7a9 refactor🎨: 调整客户端ip获取方法以及日志中对应位置更新 2021-06-13 21:20:07 +08:00
zhangwenjian ed52efaa44 refactor🎨: 修改数据字典路由注册 2021-06-13 21:16:55 +08:00
zhangwenjian 4a2aa02210 refactor🎨: 批量修改通过id获取详情返回消息 2021-06-13 21:14:17 +08:00
wenjianzhang cc0dab3cd0 refactor🎨: 开放接口无需认证 2021-06-11 17:30:17 +08:00
zhangwenjian 7b6e57b8dd Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-06-11 16:05:06 +08:00
zhangwenjian 325b2cc0a2 refactor🎨: demo环境中间件 2021-06-11 16:05:03 +08:00
wenjianzhang 52f45e362b Merge branch 'dev' of github.com:go-admin-team/go-admin into dev 2021-06-11 15:26:41 +08:00
wenjianzhang 1391366ece refactor🎨: 添加行政区模型 2021-06-11 15:26:35 +08:00
zhangwenjian 40668581b8 refactor🎨: 添加登陆验证 2021-06-11 09:26:22 +08:00
zhangwenjian 261e448577 refactor🎨: 操作日志去除菜单关联查询 2021-06-11 09:26:00 +08:00
zhangwenjian 7c7cd7ef7e refactor🎨: engine 初始化调整 2021-06-11 09:25:35 +08:00
zhangwenjian 860226ab41 refactor🎨: engine 初始化调整 2021-06-11 09:25:10 +08:00
zhangwenjian 898b1ea1e6 refactor🎨: 预览环境打包使用 2021-06-11 09:24:32 +08:00
linwenxiang f4d63c57e9 bugfix 🐛 提交遗漏代码 2021-06-11 09:20:27 +08:00
linwenxiang 3aa64d107b feature ✨ 优化setup 2021-06-10 17:15:13 +08:00
zhangwenjian 0e3e733745 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-06-10 14:47:02 +08:00
zhangwenjian e6384de265 refactor🎨: 升级依赖 2021-06-10 14:46:29 +08:00
linwenxiang b9e8759ef1 feature ✨ 支持大文件分割配置 2021-06-10 11:54:02 +08:00
zhangwenjian ff5d498c53 refactor🎨: job和代码生成模块调整 2021-06-10 11:25:39 +08:00
zhangwenjian a8820a0aac refactor🎨: 菜单以及菜单角色模块调整 2021-06-10 11:24:59 +08:00
zhangwenjian 24c78519ed refactor🎨: 数据迁移模型调整 2021-06-10 11:24:35 +08:00
zhangwenjian 49c6febdf0 refactor🎨: 日志中的位置获取函数添加key传入 2021-06-10 11:24:17 +08:00
zhangwenjian 3d620d0149 refactor🎨: 添加高德地图key自定义扩展配置 2021-06-10 11:22:53 +08:00
zhangwenjian 352224b994 refactor🎨: 修改数据迁移脚本 2021-06-10 11:22:23 +08:00
zhangwenjian 897b9e6919 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-06-09 21:36:57 +08:00
zhangwenjian 1b579fb814 feat✨: 添加修改角色状态接口 2021-06-09 21:31:31 +08:00
linwenxiang ab9020e7c7 feature ✨ gin请求纳入性能指标 2021-06-09 21:21:45 +08:00
wenjianzhang 54e116818b refactor🎨: 数据初始化 2021-06-09 18:30:47 +08:00
wenjianzhang 6a31ac916a refactor🎨: 移除v1版本中的数据迁移脚本 2021-06-09 18:25:00 +08:00
linwenxiang f35a808975 perf ⚡ 优化配置文件加载 2021-06-09 16:30:24 +08:00
linwenxiang b8ab38308d feature ✨ 参数校验支持国际化 2021-06-09 14:11:29 +08:00
wenjianzhang 91d902389a refactor🎨: 调整角色授权写法 2021-06-07 20:37:44 +08:00
wenjianzhang 5948c5c9a6 refactor🎨: 引用格式化 2021-06-07 20:37:20 +08:00
wenjianzhang 9a2c0729f6 refactor🎨: 缩减函数名称 2021-06-07 20:37:01 +08:00
wenjianzhang c96d9c35a7 refactor🎨 :支持名称和编码模糊搜索 2021-06-07 18:05:05 +08:00
zhangwenjian 65e0b58be9 refactor🎨: 去除log中间件日志打印 2021-06-07 16:41:21 +08:00
zhangwenjian dad4b4cca8 refactor🎨: 添加redis配置 2021-06-07 16:40:43 +08:00
zhangwenjian 3be02f1364 refactor🎨: 简化数据绑定 2021-06-07 16:39:59 +08:00
zhangwenjian 190dcfedc0 refactor🎨: 缩减函数名称 2021-06-07 16:39:26 +08:00
zhangwenjian 6e01c13e1c refactor🎨: 缩减函数名称 2021-06-07 16:38:30 +08:00
zhangwenjian bb77450f2c refactor🎨: 调整数据库模型字段类型,以适配其他数据库类型 2021-06-07 16:36:50 +08:00
wenjianzhang 4dfd4e4647 refactor🎨 :统一格式 2021-06-03 19:39:19 +08:00
zhangwenjian 9a49700d09 refactor🎨: 调整对象定义方式 2021-06-03 09:31:10 +08:00
zhangwenjian 107a8d8f90 docs📝: 修改GetPermissionFromContext函数注解 2021-06-03 09:30:04 +08:00
zhangwenjian b70b853016 refactor🎨: 调整格式 2021-06-03 09:29:19 +08:00
zhangwenjian dd2ed9f9a5 refactor🎨: login文档注解修改 2021-06-03 09:28:44 +08:00
zhangwenjian 1a645f8521 refactor🎨: 操作日志添加文档注解 2021-06-03 09:28:27 +08:00
zhangwenjian c05cb63bc7 refactor🎨: 改造升级用户管理模块 2021-06-03 09:27:34 +08:00
zhangwenjian 602ff26988 docs📝: 修改api文档注解 2021-06-03 09:23:48 +08:00
zhangwenjian b7d6b6786c docs📝: 添加参数api文档注解 2021-06-03 09:18:44 +08:00
zhangwenjian 95c794c80d docs📝: 修改菜单列表api文档注解 2021-06-03 09:18:11 +08:00
zhangwenjian e59dd88f97 docs📝: 更新api文档 2021-06-03 09:16:53 +08:00
wenjianzhang 974d5b2889 Merge pull request #438 from go-admin-team/1.3.x 2021-06-01 07:42:05 +08:00
zhangwenjian fb42953fbe Merge branch '1.3.x' of https://github.com/go-admin-team/go-admin into 1.3.x 2021-06-01 07:35:06 +08:00
zhangwenjian 277129e830 docs📝 update 2021-06-01 07:34:48 +08:00
wenjianzhang cdc5c1246e Merge pull request #437 from ninstein/patch-5
bugfix:必填字段判断错误修复
2021-06-01 00:21:34 +08:00
zhangwenjian c0f920d43c fix🐛 定时任务,触发函数,无法正常关闭(#432) 2021-06-01 00:17:35 +08:00
zhangwenjian f31d1a137f perf👌 调整应用中间件 2021-05-31 23:52:56 +08:00
zhangwenjian b1d3924294 perf👌 优化job处理方式 2021-05-31 23:52:32 +08:00
zhangwenjian 526a993698 fix🐛 定时任务,触发函数,无法正常关闭(#432) 2021-05-31 23:51:47 +08:00
zhangwenjian f5d9a22f83 perf👌 调整菜单代码结构 2021-05-31 23:37:04 +08:00
zhangwenjian 5ae3ef3a15 perf👌 修正菜单搜索条件信息 2021-05-31 23:36:41 +08:00
zhangwenjian 0773de2fc5 perf👌 修正接口文档配置信息 2021-05-31 23:36:20 +08:00
zhangwenjian 48a57c4244 perf👌 更新版本 2021-05-31 23:35:03 +08:00
zhangwenjian 9b1cba311e perf👌 added menu type enum 2021-05-31 23:34:50 +08:00
ninstein b5bfece9f1 bugfix:必填字段判断错误修复 2021-05-31 21:12:04 +08:00
zhangwenjian 06000a6dac perf👌 runtime接管 中间件 2021-05-31 18:11:19 +08:00
zhangwenjian a54d4ba0c1 format🥚 代码格式化 2021-05-31 18:10:23 +08:00
zhangwenjian a7cc943b8e Merge remote-tracking branch 'origin/dev' into dev 2021-05-31 18:04:59 +08:00
linwenxiang 2cb81654a6 fix 🐛 修复queue redis模式阿里云不工作问题 2021-05-31 14:43:43 +08:00
linwenxiang 0314991f9e fix 🐛 修复queue redis模式阿里云不工作问题 2021-05-31 14:39:27 +08:00
wenjianzhang da16b4af7a fix🐛 query built from **** sources 2021-05-30 22:17:11 +08:00
wenjianzhang fda4a944fc Merge pull request #434 from go-admin-team/1.3.x
1.3.x
2021-05-30 21:11:42 +08:00
wenjianzhang 9026da4a6d Merge pull request #433 from ninstein/patch-3
fix🐛 修复“关联表下拉”字段搜索未生效bug
2021-05-30 21:02:46 +08:00
ninstein 7d8c5a1ce7 bugfix:修复“关联表下拉”字段搜索未生效bug
并允许清空搜索值
2021-05-30 19:38:22 +08:00
wenjianzhang 75b10f0fb7 Merge branch 'master' into master 2021-05-29 18:20:48 +08:00
wenjianzhang 6f923d7ea7 Merge pull request #430 from go-admin-team/1.3.x
docs📝 update README
2021-05-29 15:28:56 +08:00
wenjianzhang bc2e68c288 Merge pull request #431 from ninstein/patch-2
format🥚 列表页时间字段美化
2021-05-29 15:20:51 +08:00
ninstein a02f01453d 列表页时间字段美化 2021-05-28 22:09:43 +08:00
wenjianzhang c1b0e9ab8b docs📝 update README 2021-05-28 18:36:57 +08:00
wenjianzhang ea4375b408 docs📝 update 2021-05-28 17:33:37 +08:00
wenjianzhang 1c0060fd14 Merge pull request #429 from go-admin-team/1.3.x
fix🐛 修复查询bug
2021-05-28 17:28:12 +08:00
wenjianzhang 4c127c8c4b fix🐛 remove invalid identifier character U+00A0 2021-05-28 17:18:20 +08:00
wenjianzhang 32d8f4384a feat✨ 参数更新功能 2021-05-28 17:12:55 +08:00
wenjianzhang 5be2070cbc Merge pull request #428 from ninstein/patch-1
fix🐛 修复查询bug
2021-05-28 16:08:20 +08:00
ninstein 16d98cb386 Update sys_content.go
修复查询bug
2021-05-28 15:58:07 +08:00
wenjianzhang f22c4e5dc8 Merge branch 'go-admin-team:master' into master 2021-05-28 10:17:05 +08:00
wenjianzhang d69b7807c1 feat✨ 添加修改配置接口 2021-05-27 19:33:41 +08:00
wenjianzhang dc32d92755 feat ✨ 开放部分中间件 2021-05-25 19:37:56 +08:00
wenjianzhang ca8fd2178d feat ✨ 操作日志多余参数去掉 2021-05-25 19:37:40 +08:00
wenjianzhang b565b72cd3 feat ✨ 上传文件去掉默认标识验证 2021-05-25 19:37:19 +08:00
wenjianzhang 8d272e8862 feat ✨ 排序字段接受参数名调整 2021-05-25 19:36:57 +08:00
wenjianzhang dd7b50e0fa feat ✨ 操作日志功能更换bind方式 2021-05-25 19:36:20 +08:00
wenjianzhang 623e796c92 feat ✨ 操作日志功能更换bind方式 2021-05-25 19:35:45 +08:00
wenjianzhang 126f62ce59 feat ✨ 参数设置添加接口文档 2021-05-25 19:35:05 +08:00
zhangwenjian 77c149af04 refactor🎨 api业务dto bind()、Generate()优化 2021-05-25 07:33:35 +08:00
wenjianzhang b21f665760 feat ✨ 添加队列和缓存的默认配置信息 2021-05-24 18:40:57 +08:00
wenjianzhang be36c595c3 feat ✨ 移除重复注册中间件 2021-05-24 18:40:32 +08:00
wenjianzhang 1ac6568f67 feat ✨ 日志中间件调整 2021-05-24 18:39:53 +08:00
wenjianzhang b902ab2a5f feat ✨ 用户模块添加列排序 2021-05-24 18:39:32 +08:00
wenjianzhang 5069c3074b feat ✨ 用户模块添加列排序 2021-05-24 18:39:21 +08:00
wenjianzhang e0bd26d837 feat ✨ 角色模块添加列排序 2021-05-24 18:39:00 +08:00
wenjianzhang cc74686108 feat ✨ 接口管理模块添加列排序 2021-05-24 16:30:21 +08:00
wenjianzhang 7880151ef2 refactor🎨 系统监控更名Monitor》ServerMonitor 2021-05-24 10:37:16 +08:00
wenjianzhang cfa88b5ecf refactor🎨 系统监控地址格式化 2021-05-24 10:35:52 +08:00
zhangwenjian 452a561309 feat ✨ 数据字典根据key获取 业务页面使用 2021-05-24 07:40:48 +08:00
wenjianzhang 7d7fc530d1 Merge pull request #427 from go-admin-team/1.3.x
fix🐛 修复参数根据id获取功能
2021-05-23 18:23:13 +08:00
zhangwenjian 18d9b77cca format🥚 格式化代码 2021-05-23 18:13:47 +08:00
zhangwenjian 7491ff4459 fix🐛 修复参数根据id获取功能 2021-05-23 18:12:36 +08:00
zhangwenjian 2ac8f925c9 refactor🎨 api业务功能调整 2021-05-23 17:52:43 +08:00
zhangwenjian c89b0ca8e1 Merge branch '1.3.x' of https://github.com/go-admin-team/go-admin into 1.3.x 2021-05-22 23:38:27 +08:00
zhangwenjian a3de9cd569 docs📝 修改version 2021-05-22 23:38:00 +08:00
wenjianzhang 5e10b631f6 fix🐛 修复部门数据权限 2021-05-22 23:34:44 +08:00
wenjianzhang d71ff80e12 Merge branch 'master' into 1.3.x 2021-05-22 23:28:02 +08:00
zhangwenjian 1b2bf70df1 docs📝 修改readme 2021-05-22 23:25:31 +08:00
zhangwenjian cbdfa61268 fix🐛 修复部门数据权限 2021-05-22 23:24:30 +08:00
zhangwenjian 6cbded6241 fix🐛 修复部门数据权限 2021-05-22 23:11:36 +08:00
zhangwenjian f40b300c16 docs📝 修改readme 2021-05-22 23:01:16 +08:00
wenjianzhang 8a58fb5b3b Update README.md 2021-05-22 22:58:12 +08:00
zhangwenjian e473f158e8 refactor🎨 核心业务结构调整 2021-05-22 22:54:08 +08:00
wenjianzhang 408dcc5057 refactor🎨 部分功能重写 2021-05-21 18:25:51 +08:00
wenjianzhang 765775c54d Merge branch 'dev' of github.com:go-admin-team/go-admin into dev 2021-05-20 23:00:49 +08:00
wenjianzhang 44545f3d5b feat✨ 结构统一调整 2021-05-20 22:59:09 +08:00
linwenxiang e23a1f615f feat ✨ 优化logger使用 2021-05-19 19:31:04 +08:00
linwenxiang d3fa3ecf6c Merge branch 'dev' of github.com:go-admin-team/go-admin into dev 2021-05-18 08:51:32 +08:00
wenjianzhang 6b1c3d9226 refactor🎨 api数据初始化调整 2021-05-17 23:09:35 +08:00
linwenxiang 70d4976595 Merge branch 'dev' of github.com:go-admin-team/go-admin into dev 2021-05-17 22:26:56 +08:00
wenjianzhang b2345039e4 refactor🎨 移除设置log函数 2021-05-16 21:47:49 +08:00
zhangwenjian 33275573f9 refactor🎨 链式调用改造 2021-05-16 16:17:37 +08:00
wenjianzhang 9fdde1a13b Merge pull request #424 from GizmoOAO/dev
fix🐛 修复vue模板的条件判断 #423
2021-05-15 17:55:13 +08:00
CunYu 09cc51e5de fix🐛 修复vue模板的条件判断 #423 2021-05-15 09:03:01 +08:00
wenjianzhang 20e7f79f4f refactor🎨 添加默认字段排序 2021-05-14 19:32:53 +08:00
wenjianzhang b82c3d9f99 refactor🎨 升级gin版本 2021-05-14 12:22:41 +08:00
wenjianzhang dfd460c50b feat✨ apis路径简化 2021-05-14 12:19:22 +08:00
wenjianzhang eeaa05f5b4 Merge pull request #420 from go-admin-team/1.3.x
publish🚀 1.3.5
2021-05-13 14:58:16 +08:00
wenjianzhang 4904c9ed20 publish🚀 1.3.5 2021-05-13 10:49:04 +08:00
wenjianzhang 9a6f93854b Merge pull request #419 from go-admin-team/1.3.x
publish🚀  1.3.4
2021-05-13 10:47:51 +08:00
wenjianzhang 99699c0065 feat✨ 1.3.4 2021-05-13 10:46:49 +08:00
wenjianzhang 85685dbda1 Merge pull request #418 from go-admin-team/1.3.x
gen中修改创建人和修改人设置默认值
2021-05-13 10:43:45 +08:00
wenjianzhang 3a4bfe915b feat✨ gen中修改创建人和修改人为0 2021-05-13 09:18:06 +08:00
wenjianzhang 8adc79a5f7 feat✨ 新增省市区基础数据 2021-05-13 08:45:27 +08:00
wenjianzhang 2580be08ab feat✨ 优化写法 2021-05-12 18:50:13 +08:00
wenjianzhang fd9df69d1f feat✨ 检查并写入api 2021-05-12 18:48:38 +08:00
wenjianzhang d0b2e8d03f feat✨ 添加api管理 2021-05-12 18:48:01 +08:00
wenjianzhang 70998c424d refactor🎨 优化Context设置方法 2021-05-12 18:47:29 +08:00
wenjianzhang abcdf880fd refactor🎨 优化模版 2021-05-12 18:46:23 +08:00
linwenxiang fac18b28f0 Merge branch 'dev' of github.com:go-admin-team/go-admin into dev
 Conflicts:
	common/apis/api.go
2021-05-11 22:12:14 +08:00
wenjianzhang ee15329bd2 feat✨ systables 数据迁移模块更新 2021-05-11 18:53:40 +08:00
wenjianzhang 98e0ee92a7 Default Changelist 2021-05-11 18:51:16 +08:00
wenjianzhang 54158b8824 feat✨ systables 》 sys_tables 2021-05-11 18:50:40 +08:00
wenjianzhang 75cd2f374a feat✨ gen模块文件名和路径规则调整 2021-05-11 18:49:41 +08:00
wenjianzhang a78cc33cd1 feat✨ 重置router 2021-05-11 18:48:44 +08:00
wenjianzhang fea425c36c feat✨ 分离gen router 2021-05-11 18:47:45 +08:00
wenjianzhang 61c89bca8d feat✨ 重置config router 2021-05-11 18:43:52 +08:00
wenjianzhang 76e83de8cd feat✨ 重置dept router 2021-05-11 18:42:56 +08:00
wenjianzhang c9d9350476 feat✨ 分离字典相关路由 2021-05-11 18:42:20 +08:00
wenjianzhang 34a1eea45e feat✨ 重置loginlog router 2021-05-11 18:41:43 +08:00
wenjianzhang c505884cf2 feat✨ 修改删除接口传参方式 2021-05-11 18:40:59 +08:00
wenjianzhang b95f517a4c feat✨ 重置operalog router 2021-05-11 18:39:48 +08:00
wenjianzhang 50da7825bb feat✨ 重置post路由 2021-05-11 18:38:44 +08:00
wenjianzhang 0a3e3be12c feat✨ 添加api管理 2021-05-11 18:35:55 +08:00
wenjianzhang c13d13ccf4 feat✨ 添加api结构体 2021-05-11 18:34:57 +08:00
wenjianzhang 0317c3aaf5 feat✨ 添加生成模块前端文件名 2021-05-11 18:33:29 +08:00
wenjianzhang 03eaad5b8b feat✨ 统一api生成路径 2021-05-11 08:37:27 +08:00
wenjianzhang e091603bc8 feat✨ 添加api管理功能 2021-05-10 18:24:47 +08:00
wenjianzhang cd29c1728d refactor🎨 删除模版生成提示代码 2021-05-10 18:23:41 +08:00
wenjianzhang 65b86353e3 fix🐛 修改合并代码产生的问题 2021-05-10 18:21:11 +08:00
wenjianzhang 5f594d6bae refactor🎨 修改参数删除,由url参数改为body 2021-05-10 18:20:14 +08:00
wenjianzhang 57067907d3 refactor🎨 参数设置删除修改 2021-05-10 17:25:43 +08:00
wenjianzhang bde8148eb1 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-05-10 17:04:33 +08:00
wenjianzhang ae34ff8f82 feat✨优化router import 2021-05-10 17:02:26 +08:00
wenjianzhang d91ddf265d Merge pull request #417 from go-admin-team/1.3.x
1.3.x
2021-05-10 16:45:12 +08:00
wenjianzhang 59e8645f56 fix🐛 update 2021-05-10 16:44:21 +08:00
zhangwenjian ddc3a1d403 refactor🎨 初始化gen创建者信息 2021-05-10 01:18:13 +08:00
zhangwenjian 816b10681b refactor🎨 升级go.mod 2021-05-10 01:17:21 +08:00
wenjianzhang ef33508e2b fix🐛 update 2021-05-08 18:29:14 +08:00
wenjianzhang a8c5f19075 refactor🎨 修改工具写法 2021-05-08 18:28:51 +08:00
wenjianzhang 1c046d9ddd Merge branch '1.3.x' into github-dev 2021-05-08 17:21:35 +08:00
wenjianzhang f4e07371c5 Merge pull request #413 from go-admin-team/1.3.x
fix🐛 gen问题修正
2021-05-08 17:17:55 +08:00
wenjianzhang 6a2557064c fix🐛 gen问题修正 2021-05-08 17:16:52 +08:00
wenjianzhang 4a592f515a fix🐛 文件管理\内容管理\分类程序\定时任务的已知问题(#397) 2021-05-08 17:11:59 +08:00
wenjianzhang 9bc2791b26 fix🐛 文件管理\内容管理\分类程序\定时任务的已知问题 2021-05-08 17:02:56 +08:00
wenjianzhang 6222765e20 feat✨优化配置信息 2021-05-08 16:50:51 +08:00
wenjianzhang f56839926e Merge remote-tracking branch 'github/dev' into github-dev 2021-05-08 13:53:49 +08:00
zhangwenjian c6a52134b3 feat✨优化字典数据错误判断写法 2021-05-08 13:53:27 +08:00
zhangwenjian 7fffcffcdd feat✨ 分离app 2021-05-08 09:35:49 +08:00
wenjianzhang e126fd38bc publish🚀 1.3.3 2021-05-07 20:09:13 +08:00
wenjianzhang c6d94c03fb feat✨ 更新service 2021-05-07 19:57:45 +08:00
wenjianzhang 50a5d6f48d feat✨ 更新service 2021-05-07 19:57:09 +08:00
wenjianzhang 508a18a809 feat✨ 更新readme 2021-05-07 19:11:39 +08:00
wenjianzhang 946a53cb2b feat✨ 模版格式化 2021-05-07 19:02:06 +08:00
wenjianzhang c6ccd844f5 feat✨ 去除生成方法版本信息 2021-05-07 18:54:11 +08:00
wenjianzhang debe808f83 feat✨ 优化dto模版 2021-05-07 18:52:42 +08:00
wenjianzhang e219463505 feat✨ 升级模版 2021-05-07 18:46:12 +08:00
wenjianzhang 009866ee31 feat✨ :更新api写法,同步调整模版 2021-05-07 17:56:15 +08:00
linwenxiang b0225d9bab feat ✨ 增加bind通用方法 2021-04-27 11:48:03 +08:00
linwenxiang 13ff9d9e63 feat ✨ 优化api写法 2021-04-27 10:06:22 +08:00
wenjianzhang 5957fb280c config🔧 :修改配置文件 2021-04-27 10:06:22 +08:00
wenjianzhang 44d196eaef config🔧 :修改配置文件 2021-04-27 10:06:22 +08:00
linwenxiang bf7934c20a fix 🐛 优化迁移功能 2021-04-26 14:47:31 +08:00
linwenxiang 845558cc65 fix 🐛 修复初始化数据库账号登录不了问题 2021-04-26 10:45:45 +08:00
lwnmengjing 22250dbbba Merge pull request #400 from go-admin-team/dev
migrate ✅ 优化migrate
2021-04-23 15:37:13 +08:00
linwenxiang 7290742ac0 dep ⬇️ 升级依赖 2021-04-23 15:33:59 +08:00
linwenxiang 97d9590d05 doc ✅ 修复swagger文档 2021-04-23 14:59:53 +08:00
linwenxiang 1a111f0666 migrate ✅ 优化migrate 2021-04-23 14:25:31 +08:00
linwenxiang e40047a88e feat ✨ 去除数据库写log对queue驱动的依赖,调整cache,分离出queue和locker 2021-04-20 11:08:46 +08:00
wenjianzhang 3df83cb057 Merge pull request #396 from jfcg/master
Create codeql-analysis.yml
2021-04-13 20:46:10 +08:00
Serhat Şevki Dinçer cfd284e42e rm schedule from codeql 2021-04-13 00:01:00 +03:00
wenjianzhang 3211b47d40 Update README.md 2021-04-12 22:40:43 +08:00
Serhat Şevki Dinçer f21de0706a Create codeql-analysis.yml 2021-04-12 15:44:02 +03:00
wenjianzhang 7abe98b723 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-04-09 12:33:45 +08:00
linwenxiang 413ca6d353 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	scripts/k8s/deploy.yml
2021-04-08 21:41:42 +08:00
linwenxiang b69bc87468 feat ✨ 验证码store支持go-admin cache 2021-04-08 21:33:36 +08:00
wenjianzhang a1a41f0a86 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-04-08 18:43:53 +08:00
wenjianzhang 14db1e802c feat ✨: 添加字段验证 2021-04-08 18:43:17 +08:00
wenjianzhang 221a47423d fix🐛 :修改扩展配置文件名称 2021-04-08 17:17:13 +08:00
wenjianzhang daadb14618 fix🐛 :修改扩展配置文件名称 2021-04-08 17:13:42 +08:00
linwenxiang 40cd55da90 ci 💚 修改k8s yaml配置 2021-04-08 13:53:44 +08:00
linwenxiang eddfe26308 feat ✨ 验证码store支持go-admin cache 2021-04-07 23:01:32 +08:00
linwenxiang e034e5b1ea ci 💚 修改k8s yaml配置 2021-04-06 18:10:53 +08:00
linwenxiang 36bdeb3faa ci 💚 修改k8s yaml配置 2021-04-06 18:09:23 +08:00
linwenxiang 7112323a5e perf 👌: 统一配置参数 2021-03-31 21:56:38 +08:00
linwenxiang ea4ffb8867 Merge branch 'dev' 2021-03-31 21:07:16 +08:00
linwenxiang 225970c005 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	go.mod
2021-03-31 21:02:59 +08:00
linwenxiang 24370899ab perf 👌: 升级go-admin-core依赖 2021-03-31 21:02:12 +08:00
wenjianzhang 510bdd94f9 docs📝 :修正版本信息 2021-03-31 19:45:07 +08:00
wenjianzhang 2a27c91656 config🔧 :core升级 2021-03-31 19:44:08 +08:00
wenjianzhang 3767274755 config🔧 :修改配置文件 2021-03-31 19:09:47 +08:00
wenjianzhang aefdb47c32 feat ✨: 配置文件扩展项使用 2021-03-31 19:06:25 +08:00
wenjianzhang 30feaae7e7 fix🐛 :修复文件路径判断函数 (#382) 2021-03-31 18:59:58 +08:00
lwnmengjing 8557e1e160 Merge pull request #384 from go-admin-team/dev
admin中的公共中间件,提到common 495a9c5
2021-03-30 22:56:52 +08:00
linwenxiang dd16744676 refactor 🎨: admin中的公共中间件,提到common 2021-03-29 09:05:05 +08:00
linwenxiang 495a9c54aa refactor 🎨: admin中的公共中间件,提到common 2021-03-28 23:27:01 +08:00
wenjianzhang afab1ef7c9 docs📝 :修正readme 2021-03-26 15:08:15 +08:00
wenjianzhang 5e99363e9a docs📝 :修正readme 2021-03-26 15:06:12 +08:00
wenjianzhang 86c7c64a32 fix🐛 :修复角色数据权限更新失(#380) 2021-03-26 14:36:18 +08:00
wenjianzhang 44b9666302 fix🐛 :修复内容模块问题(#357)
fix🐛 :修复内容模块问题(#357)
2021-03-26 13:53:44 +08:00
wenjianzhang 91c9a2c7f9 fix🐛 :修复内容模块问题(#357) 2021-03-26 13:42:12 +08:00
wenjianzhang a65930b35b fix🐛 :修复已知bug (#377) 2021-03-26 09:03:21 +08:00
linwenxiang 9023b620fb Merge branch 'master' into dev 2021-03-25 22:28:24 +08:00
linwenxiang f188b34253 feat ✨: 使用runtime cache中的mq功能优化日志存储
perf 👌: 启用mq将日志存储从中间件中移除到消费者
2021-03-25 22:26:33 +08:00
wenjianzhang 8faf6c9aba docs📝 :更新readme 2021-03-25 19:14:42 +08:00
wenjianzhang 547a5eb8e4 fix🐛 :修复初始化Duplicate column name 'is_frontend' (#376) 2021-03-25 18:35:09 +08:00
linwenxiang 2459c877e0 Merge branch 'dev' 2021-03-25 14:47:12 +08:00
linwenxiang 93625a6d9f format 🥚:模版生成优化 2021-03-25 14:45:39 +08:00
linwenxiang 58b439f553 修复个人中心修改密码不生效问题 2021-03-25 11:34:30 +08:00
linwenxiang 78c67185fd 升级core依赖 2021-03-24 23:00:09 +08:00
lwnmengjing 265cc9350d Merge pull request #374 from go-admin-team/dev
优化生成的swagger
2021-03-24 22:49:33 +08:00
linwenxiang 4182bac3a2 修改模版,优化生成的swagger 2021-03-24 22:20:23 +08:00
linwenxiang 12893fc09d 修复swagger无法生成文档 2021-03-24 18:52:59 +08:00
linwenxiang 6fb7da87a3 优化logger设置获取流程 2021-03-24 16:49:04 +08:00
wenjianzhang 0ecf56cd8c docs📝 :更新 2021-03-17 19:21:28 +08:00
wenjianzhang 0c802ad366 feat✨ :菜单配置迁移脚本功能 2021-03-17 19:15:49 +08:00
wenjianzhang 0be7735855 publish🚀 v1.3.0 2021-03-17 16:42:47 +08:00
wenjianzhang 9c73558382 feat✨ :更新 2021-03-17 16:29:45 +08:00
wenjianzhang 4e370665dd feat✨ :去除系统配置表数据的sql 2021-03-17 16:26:28 +08:00
wenjianzhang 0194561056 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-03-17 14:30:20 +08:00
wenjianzhang 5b69883a9f fix🐛 :系统配置初始化sql 2021-03-17 14:30:16 +08:00
linwenxiang b1789e5785 优化api返回方法 2021-03-17 12:43:52 +08:00
wenjianzhang 5756286641 fix🐛 :去掉迁移脚本中的数据库名称 2021-03-17 11:23:17 +08:00
wenjianzhang 6746dbd9cf feat✨ :参数配置中添加是否前台控制 2021-03-17 11:16:53 +08:00
wenjianzhang 2134d9fa58 fix🐛 :修复日志状态显示问题 2021-03-17 11:16:02 +08:00
wenjianzhang a8c00c5be1 fix🐛 :修复请求日志数据获取为空问题 2021-03-17 11:15:35 +08:00
wenjianzhang 51bc4423f7 feat✨ :系统配置统一使用参数设置功能
注意:系统配置表将设置为过时功能
2021-03-17 10:18:47 +08:00
linwenxiang 55a85babf8 升级go-admin-core依赖 2021-03-16 17:21:17 +08:00
linwenxiang c9f705e4f5 Merge remote-tracking branch 'origin/dev' into dev 2021-03-12 16:03:12 +08:00
linwenxiang 9bf9330410 api层写法统一 2021-03-12 16:02:29 +08:00
wenjianzhang f53ee3008a Update index.go 2021-03-12 12:14:54 +08:00
wenjianzhang 31ec3880da fix🐛 :修改接口默认页面地址 2021-03-12 12:14:15 +08:00
lwnmengjing ed492d5411 Merge pull request #372 from go-admin-team/dev
修复日志插件报错问题
2021-03-11 23:58:14 +08:00
linwenxiang fa30b8db95 修复日志插件报错问题 2021-03-11 23:36:56 +08:00
linwenxiang 8a471e1db7 修复模版不兼容actions模式生成
迁移重复问题
2021-03-11 22:19:12 +08:00
linwenxiang dbf4b36687 修复requestId不一致问题 2021-03-11 21:06:14 +08:00
wenjianzhang 263355de1e Merge pull request #371 from go-admin-team/dev
1.3.0rc.0
2021-03-11 16:43:29 +08:00
wenjianzhang 3a570e50a1 合并 2021-03-11 15:48:00 +08:00
wenjianzhang e0767255f4 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-03-11 15:47:51 +08:00
wenjianzhang 75b529d7b0 ormat🥚 :优化生成模版 2021-03-11 14:56:15 +08:00
wenjianzhang a43413d1ff ormat🥚 :优化代码 2021-03-11 14:54:34 +08:00
lwnmengjing 7599a22470 Merge pull request #370 from go-admin-team/dev
gorm日志支持显示requestId
2021-03-11 13:43:25 +08:00
linwenxiang b79747e551 gorm日志支持显示requestId 2021-03-11 13:39:43 +08:00
lwnmengjing 4666025819 Merge pull request #369 from go-admin-team/dev
修复sql问题
2021-03-10 22:25:24 +08:00
linwenxiang 5353527e6c 更新gitignore 2021-03-10 22:21:44 +08:00
linwenxiang 2bfd8c51b1 修复sql问题
修改gorm log
2021-03-10 22:19:10 +08:00
lwnmengjing 95b3b97597 Merge pull request #368 from go-admin-team/dev
Dev
2021-03-10 15:39:00 +08:00
linwenxiang 31b36bc413 修复部门名称不显示问题
close #367
2021-03-10 15:33:51 +08:00
linwenxiang db7abdda33 更新sdk依赖 2021-03-10 11:35:19 +08:00
linwenxiang 25d8364bf4 修改,兼容go-admin sdk方案 2021-03-09 19:12:49 +08:00
lwnmengjing c541d09076 升级版本号 2021-03-08 22:53:02 +08:00
linwenxiang c2ceaec3ea Merge branch 'dev' 2021-03-08 22:34:36 +08:00
linwenxiang 3e26bf136d 修复初始化数据问题 2021-03-08 20:31:33 +08:00
linwenxiang 21a81a534c 部分bug修复 2021-03-07 15:37:14 +08:00
wenjianzhang 00b46b79df Delete .DS_Store 2021-03-07 10:34:32 +08:00
linwenxiang 9c7099db1f 修复初始化数据时问题 2021-03-05 19:01:05 +08:00
linwenxiang 86700cfd52 修复初始化数据时问题 2021-03-05 17:03:56 +08:00
linwenxiang a1b74e3f6a Merge remote-tracking branch 'origin/dev' into dev 2021-03-05 15:13:59 +08:00
linwenxiang 75711922b4 调整log代码,兼容zap扩展支持fields 2021-03-05 15:13:36 +08:00
lwnmengjing f02c178473 Delete .DS_Store
删除无用文件
2021-03-05 13:49:53 +08:00
linwenxiang 39dd1ee6f8 兼容casbin2.24.0版本log
迁移脚本编译时不打包
2021-03-05 13:23:21 +08:00
linwenxiang 48f0cb46ec 修复新代码报错 2021-03-05 00:06:21 +08:00
linwenxiang 1105d98f27 修改新代码兼容 2021-03-04 23:52:00 +08:00
linwenxiang c294bb1585 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	template/v4/dto.go.template
2021-03-04 23:47:14 +08:00
linwenxiang e2952fa393 调整整体架构写法
调整db用法
调整日志用法
调整模版
2021-03-04 23:45:16 +08:00
zhangwenjian 12be09b4d5 feat✨ :工作流分类 2021-02-26 16:15:35 +08:00
wenjianzhang d8ebaf540b feat✨ :文件名称调整 2021-02-26 01:25:36 +08:00
wenjianzhang b8ec705e0a feat✨ :添加行政区域基础数据 2021-02-26 01:05:20 +08:00
zhangwenjian 19e96a01de feat✨ :模版优化 2021-02-25 23:40:55 +08:00
linwenxiang 734a48dbb8 兼容代码生成 2021-02-25 14:24:09 +08:00
linwenxiang 78671ae99e 升级核心依赖库 2021-02-25 11:28:37 +08:00
linwenxiang 7e7065a579 升级流水线编译go版本 2021-02-25 10:39:17 +08:00
linwenxiang 5ff282c23d feat✨ :添加资源管理中的需要开放的必开接口 2021-02-25 01:32:06 +08:00
zhangwenjian 117392bfe8 format🥚 :优化文件名 2021-02-24 14:23:13 +08:00
zhangwenjian f22f654c91 format🥚 :调整主键key和自增长tag 2021-02-24 14:22:33 +08:00
zhangwenjian 561566f172 fix🐛 :修改内容分页地址 2021-02-23 17:50:53 +08:00
zhangwenjian c3bb36c969 fix🐛 :部门path创建初始化 2021-02-23 17:44:20 +08:00
linwenxiang da347e095f 权限相关问题修复 2021-02-22 22:00:34 +08:00
zhangwenjian e9617ec677 fix🐛 :格式化内容和job的文件名 2021-02-22 16:18:24 +08:00
zhangwenjian 017d669a04 fix🐛 :修复通过字典类型获取字段数据的路有变更 2021-02-22 15:44:04 +08:00
zhangwenjian e184ad84cd docs📝 :更新readme 2021-02-22 14:54:54 +08:00
linwenxiang deeb82048b 数据库连接初始化优化
权限部分修改
2021-02-21 14:06:13 +08:00
linwenxiang 4979fd659f 完成字典模块改造 2021-02-20 11:06:59 +08:00
linwenxiang e04b5559bd 前后端一致修复 2021-02-19 16:50:57 +08:00
zhangwenjian a4f8b11925 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2021-02-19 10:48:33 +08:00
zhangwenjian 9186c6ce04 fix:优化代码 2021-02-19 10:48:27 +08:00
linwenxiang a24c31c6b9 修复菜单列表、角色列表接口报错问题 2021-02-19 10:01:45 +08:00
linwenxiang 981e992f28 不全多删除的部分 2021-02-05 10:30:48 +08:00
linwenxiang 7ce090af38 删除多余打印 2021-02-05 10:24:11 +08:00
linwenxiang f67e653abe 整理requestId入口,兼容istio的header返回 2021-01-29 14:53:52 +08:00
linwenxiang 128739f097 优化response结构
优化日志writer
2021-01-20 00:31:52 +08:00
wenjianzhang 792c8874aa perf👌 : 岗位、角色功能改造 2021-01-16 21:46:57 +08:00
zhangwenjian ad42b6489a Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2021-01-16 15:05:16 +08:00
zhangwenjian 8a3659d949 perf👌 : 部门和菜单功能改造 2021-01-16 15:05:12 +08:00
wenjianzhang a10bacc1f5 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-01-14 22:41:57 +08:00
wenjianzhang 677e54631a perf👌 : 模版升级 2021-01-14 22:41:49 +08:00
zhangwenjian c0f6fb6b41 feat✨ : 添加请求无权限log 2021-01-14 16:03:13 +08:00
zhangwenjian 24ab2d9cbe Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2021-01-12 13:22:08 +08:00
zhangwenjian f768d69def feat✨ : 没有权限的接口使用log输出 2021-01-12 13:22:03 +08:00
zhangwenjian 305326d9ea feat✨ : 登陆日志数据的调整 2021-01-12 13:21:38 +08:00
zhangwenjian 249567d61d feat✨ : 初始化数据的调整 2021-01-12 13:21:07 +08:00
wenjianzhang 390f4ddfdd Merge branch 'dev' of https://github.com/go-admin-team/go-admin into dev 2021-01-11 21:39:02 +08:00
wenjianzhang 72d92b5232 Update 1606582844753_migrate.go 2021-01-11 21:38:55 +08:00
zhangwenjian 82a09c4abd perf👌 : 系统配置功能优化 2021-01-11 21:31:03 +08:00
zhangwenjian a7dafceb5b perf👌 : 系统配置功能优化 2021-01-11 21:29:57 +08:00
zhangwenjian 98d30f64f6 perf👌 : logger 添加传入传出项 2021-01-11 16:27:36 +08:00
zhangwenjian abe9c42aff perf👌 : 登陆日志和操作日志功能优化 2021-01-11 16:27:07 +08:00
zhangwenjian 0db58170ee perf👌 : 配置文件功能代码优化 2021-01-11 16:26:12 +08:00
zhangwenjian e1f15bcff1 添加返回log输出 2021-01-11 09:34:53 +08:00
linwenxiang 17032f29a0 支持drone流水线,发布到k8s 2020-12-23 20:27:18 +08:00
zhangwenjian 0db2e8ef13 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2020-12-21 23:01:37 +08:00
zhangwenjian 2c3dda58b4 Update .gitignore 2020-12-21 22:28:40 +08:00
zhangwenjian eaa135e2ad refactor🎨 : 修正模版 2020-12-21 22:04:46 +08:00
zhangwenjian 46ea5f18aa refactor🎨 : uint 调整为 int 2020-12-21 22:04:20 +08:00
zhangwenjian ab7c34f817 refactor🎨 : 管理员id 类型修改 2020-12-21 22:03:12 +08:00
zhangwenjian 746123c888 refactor🎨 : 修改文件操作函数 2020-12-21 22:02:40 +08:00
zhangwenjian 37a2c585e1 refactor🎨 : 删除过时的文件管理函数 2020-12-21 22:01:03 +08:00
zhangwenjian d5a94b8dc0 fix🐛 :系统配置编辑无效(#293) 2020-12-21 16:45:53 +08:00
linwenxiang da96902ca4 修改日志输出模式:
单文件,在内容中区分,方便对接各种日志系统
2020-12-20 19:45:04 +08:00
linwenxiang d9347de392 修改工具库目录
更新gorm到最新版
2020-12-10 17:55:33 +08:00
linwenxiang 9731b7baaf 整合三方依赖 2020-12-10 10:45:14 +08:00
linwenxiang 0c170fe1c6 config setup配置提到外面 2020-12-10 10:24:06 +08:00
linwenxiang 5f873914e0 config重构 2020-12-08 20:28:26 +08:00
linwenxiang 0810383afc Merge branch 'dev' of github.com:go-admin-team/go-admin into dev 2020-12-02 21:12:18 +08:00
linwenxiang 2790abf1ad 修复cache包问题 2020-12-02 21:11:53 +08:00
wenjianzhang ddb3a45e1a fix:🐛 修复字典状态的过滤问题(#251) 2020-11-29 01:37:32 +08:00
wenjianzhang 83529dd6f5 fix:🐛 修复部门创建时间显示问题和状态显示问题(#225) 2020-11-29 01:05:43 +08:00
wenjianzhang b9e9d7178b fix:🐛 重新生成文档,并修复之前引用结构体错误问题 2020-11-29 00:27:52 +08:00
wenjianzhang ab547cc49b refactor:🎨 资源管理模块代码升级,移除旧代码 2020-11-29 00:26:17 +08:00
wenjianzhang 6b0b1bb836 fix:🐛 修复用户停用状态还能登陆问题(#287) 2020-11-29 00:19:11 +08:00
wenjianzhang 9d6e3598c0 Merge pull request #309 from go-admin-team/dev
feat✨ 增加条件编译,修复windows下gcc依赖问题
2020-11-25 23:51:50 +08:00
linwenxiang 93c5511b55 增加条件编译,修复windows下gcc依赖问题 2020-11-24 13:16:19 +08:00
wenjianzhang 3e7b3f5176 Merge pull request #307 from go-admin-team/dev
fix🐛 : 日志目录bug
2020-11-22 02:53:17 +08:00
wenjianzhang a958c879a1 feat✨ 注释Sort字段 2020-11-22 02:51:15 +08:00
wenjianzhang a06da857f3 fix🐛 资源管理去掉排序字段 2020-11-14 22:12:15 +08:00
wenjianzhang 5714ffde7b fix🐛 修复非管理员角色无法配置权限问题(#292) 2020-11-14 01:02:02 +08:00
wenjianzhang f1e521522a perf👌 优化资源选择组件目录结构问题 2020-11-14 00:16:50 +08:00
zhangwenjian 52be3f6153 feat✨ oss 文件上传 2020-11-12 20:18:53 +08:00
linwenxiang 2399824d35 fixme: 优化初次启动日志文件创建 2020-11-04 11:26:41 +08:00
zhangwenjian 17b850d0a8 Update go.sum 2020-11-03 15:13:54 +08:00
zhangwenjian 78db91785c Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2020-11-03 15:13:18 +08:00
zhangwenjian 411b9d3380 config 🔧 修改go.mod版本信息 2020-11-03 15:07:20 +08:00
linwenxiang 7382a44162 go-admin-core依赖1.2.2版本 2020-11-03 11:35:14 +08:00
wenjianzhang 07cb061ea5 publish 🚀 1.2.2版本发布
1. admin 角色权限调整默认全部权限
1. 优化内容分类管理
1. 系统登录日志和操作日志代码优化
1. 性能指标、健康检查、default log配置生效
1. 日志全部改为go-admin-core库日志,去除对其他项目的依赖
1. 去除pkg/auth对tools依赖,减少循环引用可能性
1. 增加链路追踪,熟悉的朋友可以将go-admin作为微服务的网关使用😄
1. 代码优化
1. 部分已知bug修复
2020-11-03 10:58:29 +08:00
linwenxiang 5078403569 支持多链路 2020-10-30 17:16:47 +08:00
linwenxiang 5e3e16025c 去除pkg/auth对tools依赖,减少循环引用可能性 2020-10-30 15:40:51 +08:00
linwenxiang 4b5b0507e9 增加链路追踪,熟悉的朋友可以将go-admin作为微服务的网关使用😄 2020-10-30 14:52:49 +08:00
linwenxiang ccd76445f2 日志全部改为go-admin-core库日志,去除对其他项目的依赖 2020-10-29 19:22:23 +08:00
linwenxiang 706c72b973 增加注释 2020-10-29 14:48:25 +08:00
zhangwenjian 22fb2b5d0b fix🐛 : 修复log获取不到db 2020-10-28 17:13:16 +08:00
zhangwenjian 16775cee45 refactor🎨 : 修改日志相关功能,针对代码进行优化 2020-10-27 13:13:44 +08:00
zhangwenjian 2195c89099 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2020-10-27 11:39:49 +08:00
zhangwenjian 199fc180b7 refactor🎨 : 系统登录日志和操作日志代码优化 2020-10-27 11:38:49 +08:00
zhangwenjian 55112db749 refactor🎨 : 针对数据库名称部分添加符号包括 (#281) 2020-10-27 11:23:56 +08:00
linwenxiang da4637d285 logger模块移入go-admin-core仓库,修改log用法暂时折中
fixme:后期要尽快改成统一的log处理方式
2020-10-26 22:37:44 +08:00
linwenxiang a68e58f93b 删除无用目录,修改无用代码 2020-10-26 15:49:14 +08:00
zhangwenjian c814439f93 feat✨ admin角色编辑权限改为默认 2020-10-24 13:14:57 +08:00
wenjianzhang cc34268791 Merge pull request #271 from kikiyou/dev
v1.2.1支持sqlite3
2020-10-23 23:53:05 +08:00
zhangwenjian b94acb62ca Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2020-10-23 23:35:43 +08:00
zhangwenjian c851e82762 docs:文档更新 2020-10-23 23:34:32 +08:00
zhangwenjian 870b144557 feat : admin 角色权限调整默认全部权限 2020-10-23 23:33:57 +08:00
zhangwenjian ac5b10e6c0 refactor: 优化内容分类管理 2020-10-23 23:32:44 +08:00
linwenxiang e1965be2ab 性能指标、健康检查、default log配置生效 2020-10-21 18:03:39 +08:00
wenjianzhang b931925f75 fix:初始化兼容问题 (#274)
fix:初始化兼容问题 (#274)
2020-10-21 15:52:32 +08:00
zhangwenjian e53cc4a9b5 version upgrade 2020-10-21 15:44:39 +08:00
zhangwenjian b40d376e25 Merge branch 'dev' of https://github.com/go-admin-team/go-admin into go-admin-team-dev 2020-10-21 14:13:17 +08:00
linwenxiang 52f42616a2 近期bug修复,迁移go-admin-core库, Fixed #272 Fixed #261 2020-10-21 10:09:47 +08:00
kikiyou 01c519e894 v1.2.1支持sqlite3 2020-10-19 18:10:35 +08:00
zhangwenjian 93feb4779e 修改验证码为4位数字 2020-10-17 18:54:40 +08:00
wenjianzhang 61bf06c49b v1.2.1
1. 登陆页面适配移动端
2. 调整sysconfig功能
3. 修复已知BUG
2020-10-17 17:58:30 +08:00
wenjianzhang 8ef816c2a9 Merge pull request #263 from wenjianzhang/dev
调整sysconfig结构
2020-10-17 17:56:20 +08:00
zhangwenjian 8705ad0436 调整sysconfig结构 2020-10-17 17:52:20 +08:00
wenjianzhang c350c8e32a Merge pull request #260 from go-admin-team/dev
优化sysconfig功能
2020-10-14 13:50:08 +08:00
wenjianzhang f2703f16d5 Merge pull request #259 from wenjianzhang/dev
Update 1602644950000_migrate.go
2020-10-14 12:19:59 +08:00
zhangwenjian 7a0fff117b Update 1602644950000_migrate.go 2020-10-14 12:18:39 +08:00
wenjianzhang 209db87d05 Merge pull request #258 from wenjianzhang/dev
优化sysconfig 业务,添加生成迁移文件命令
2020-10-14 11:59:12 +08:00
zhangwenjian d12b1b7384 添加生成迁移文件命令 2020-10-14 11:57:20 +08:00
zhangwenjian 21603d5a4c 优化sysconfig 业务 2020-10-14 11:42:01 +08:00
wenjianzhang bd658d9f0b Merge pull request #256 from go-admin-team/dev
fix:修复httpjob循环调用bug
2020-10-13 22:27:47 +08:00
wenjianzhang 9e640cf422 Merge pull request #255 from wenjianzhang/dev
fix:修复httpjob循环调用bug
2020-10-13 22:25:09 +08:00
zhangwenjian d1d545b29c fix:修复httpjob循环调用bug 2020-10-13 22:19:59 +08:00
linwenxiang 1e8544f1ce 支持限流,200QPS 2020-10-12 20:15:28 +08:00
wenjianzhang 2ecabd1202 Merge pull request #253 from go-admin-team/dev
整合gorm的log
2020-10-12 10:57:36 +08:00
linwenxiang 4741137c9a 整合gorm的log 2020-10-09 19:32:08 +08:00
wenjianzhang f4c5aacf32 Merge pull request #249 from go-admin-team/dev
fix:fix tpl gen bug (#248)
2020-10-09 17:48:32 +08:00
wenjianzhang 72a95babe5 fix:fix tpl gen bug (#248)
readme:update msg
2020-10-09 17:47:31 +08:00
zhangwenjian 3b5ce7ffaf bug:fix tpl gen bug (#248) 2020-10-09 17:46:16 +08:00
zhangwenjian 7db918c495 readme:update msg 2020-10-09 16:46:04 +08:00
linwenxiang faa8de7dc2 修改日志初始化值,level值不被覆盖 2020-10-09 16:15:51 +08:00
wenjianzhang 68c51fb480 Merge pull request #246 from go-admin-team/dev
feat:readme update
2020-10-08 20:17:34 +08:00
wenjianzhang 4fe68183ba Merge pull request #245 from wenjianzhang/dev
feat:readme update
2020-10-08 20:16:36 +08:00
zhangwenjian 918e5c2ce1 Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-10-08 20:13:51 +08:00
zhangwenjian ce656bf7a1 readme:update team 2020-10-08 20:13:45 +08:00
wenjianzhang 4be26aa5d2 v1.2.0
添加资源管理
添加资源统一组件
代码生成工具支持关系表
调整了项目结构
添加了路由主动注册机制
添加了数据库迁移功能
添加无代码CRUD函数
添加通用CRUD函数
修复了部分已知bug
2020-10-05 18:29:32 +08:00
linwenxiang dd09f52343 修复字典值查重问题 2020-10-03 09:15:58 +08:00
zhangwenjian 606aa821d5 clear:clear no used file 2020-09-29 23:00:18 +08:00
zhangwenjian 99acf0086f feat:update file struct 2020-09-29 22:48:03 +08:00
zhangwenjian c9b073908e feat:clear no used file go 2020-09-29 22:32:53 +08:00
zhangwenjian 000cd1d07d feat:update sql init 2020-09-29 16:32:17 +08:00
zhangwenjian ca8d1e7e33 feat:update tpl 2020-09-29 12:23:16 +08:00
zhangwenjian bc648c3f2f test 2020-09-29 08:41:20 +08:00
linwenxiang 677cdb7821 修改参数验证,统一返回200 2020-09-24 21:47:48 +08:00
linwenxiang 7e2e40df56 修改随机字符串生成方法 2020-09-23 22:26:14 +08:00
zhangwenjian 2c23ef8c6d Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-09-23 00:38:44 +08:00
zhangwenjian dab93f16b0 tpl:update dto template 2020-09-23 00:38:38 +08:00
linwenxiang 43dd214a7e 修改模版兼容 2020-09-23 00:36:37 +08:00
linwenxiang c0ac9a6397 支持view自定义返回 2020-09-23 00:35:07 +08:00
linwenxiang 78bb246390 支持多应用路由注册示例写法 2020-09-22 23:18:54 +08:00
linwenxiang 029783a685 修复job删除问题 2020-09-22 22:16:58 +08:00
linwenxiang 8460b3f385 修改action和模版,使用log打印日志,修复job删除问题,已经所有删除问题 2020-09-22 22:04:29 +08:00
linwenxiang f3e0e4dbc6 简化代码生成 2020-09-22 20:39:11 +08:00
linwenxiang 72a876f37a 日志生效,
调整路由注册方式
2020-09-21 23:24:50 +08:00
linwenxiang 7d8a267da7 日志生效,
调整路由注册方式
2020-09-21 23:23:40 +08:00
linwenxiang 2fe2c96e82 语法规范 2020-09-21 13:07:41 +08:00
linwenxiang fdbb76086e 增加go-admin日志规范用法 2020-09-21 13:04:48 +08:00
linwenxiang a8d5846483 优化casbin接口鉴权问题,不再重复查询数据库 2020-09-21 11:39:19 +08:00
linwenxiang a06bd289b3 升级gorm 2020-09-17 21:04:10 +08:00
linwenxiang 6bc3e6c00b 优化模版生成 2020-09-16 22:42:13 +08:00
linwenxiang 91f2adb340 修复模版生成bug
关联表
新增字段选择
2020-09-16 21:12:13 +08:00
linwenxiang 21d217c42a 修复模版生成bug
关联表
新增字段选择
2020-09-16 20:59:53 +08:00
linwenxiang 50464dc6e6 修改忽略文件 2020-09-15 23:00:14 +08:00
linwenxiang bb07d90407 增加service实现 2020-09-15 13:59:01 +08:00
linwenxiang 3d5a2ea9f7 修复初始化已知bug 2020-09-14 21:52:08 +08:00
linwenxiang ca4ed89536 Merge branch 'dev' of github.com:wenjianzhang/go-admin into table/dev
 Conflicts:
	template/v2/dto.go.template
	template/v2/model.go.template
2020-09-14 20:06:35 +08:00
linwenxiang 9cab65af6a 模版修改 2020-09-14 20:02:55 +08:00
zhangwenjian 7bba9b44b5 tpl:update tpl 2020-09-13 22:29:43 +08:00
zhangwenjian fd27170946 feat:update tpl 2020-09-13 17:57:11 +08:00
linwenxiang c6735c4b1a Merge branch 'dev' of github.com:wenjianzhang/go-admin into table/dev 2020-09-12 21:38:32 +08:00
linwenxiang 106ca70acc 修改引用 2020-09-12 21:37:40 +08:00
zhangwenjian e0f5a9ede2 feat:update tpl 2020-09-12 21:25:27 +08:00
zhangwenjian ab28eea2f3 update:added gen init data 2020-09-12 20:38:54 +08:00
zhangwenjian 6f09ecca32 fix:init data sql 2020-09-12 20:11:30 +08:00
zhangwenjian 0b80ac635d fix:tpl bug 2020-09-12 20:06:13 +08:00
zhangwenjian 31544ff1ae doc:update readme 2020-09-12 02:09:10 +08:00
zhangwenjian d4b87c78a7 feat:date migrate 2020-09-12 01:27:39 +08:00
zhangwenjian b8f8bfd400 Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-09-12 01:08:35 +08:00
zhangwenjian 2d15f91f2e feat:update gen 2020-09-12 01:07:35 +08:00
linwenxiang 3a6a06f52a 修改数据表迁移用法 2020-09-12 00:15:23 +08:00
linwenxiang 317d417824 升级go-admin-core库 2020-09-11 23:03:14 +08:00
zhangwenjian 8a7a9db7fd feat:update api model router tpl 2020-09-10 14:14:08 +08:00
zhangwenjian fa07a57d22 feat : update actions gen 2020-09-10 02:14:48 +08:00
zhangwenjian aaba20d42a Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-09-09 22:45:03 +08:00
zhangwenjian 4466482ad9 feat:update gen 2020-09-09 22:43:21 +08:00
linwenxiang 4140cad9dd 增加通用配置组件 2020-09-09 20:17:08 +08:00
linwenxiang 0c64d8b927 修复指针问题,提取by_id到公共结构体 2020-09-08 20:28:31 +08:00
linwenxiang 3bd3656d15 迁移方法set时增加锁 2020-09-04 14:33:46 +08:00
linwenxiang 77dfe418df 构建数据库迁移体系 2020-09-04 14:30:52 +08:00
wenjianzhang 8a3ee2e7af Merge pull request #231 from matchstalk/table/dev
添加数据权限
2020-09-04 00:37:02 +08:00
linwenxiang 01ea376015 修改文件目录 2020-09-04 00:21:52 +08:00
linwenxiang 41b06c806a 修改createBy和updateBy为uint类型 2020-09-03 22:14:34 +08:00
linwenxiang 0ae41f247c 兼容批量删除 2020-09-03 21:04:17 +08:00
linwenxiang 560e650070 拆分数据权限验证,让用户选择是否加 2020-09-03 19:52:15 +08:00
linwenxiang 3a9831fc8c 删除角色时,检查是否有用户绑定 2020-09-03 11:51:25 +08:00
linwenxiang 4b8c372038 1、action中各动作添加数据权限
2、修改jwt中key为常量
2020-09-03 10:49:31 +08:00
wenjianzhang d3622c93fc Merge pull request #226 from matchstalk/table/dev
调整indexAction,允许用户自定义接收的slice,配合gorm只能搜索
2020-09-01 20:12:05 +08:00
linwenxiang f0fde7f77c 规范curd动作流程 2020-09-01 13:37:49 +08:00
linwenxiang c93f3f01b3 格式化 2020-08-31 22:09:00 +08:00
linwenxiang 4e39e915ea 调整indexAction路径,允许用户自定义接收的slice,配合gorm只能搜索 2020-08-31 21:56:29 +08:00
zhangwenjian b98997a69f feat:update userinfo 2020-08-31 21:18:14 +08:00
zhangwenjian cb06f596e9 update: update router template 2020-08-30 14:43:29 +08:00
wenjianzhang 638e5b8311 Merge pull request #222 from matchstalk/table/dev
使用action代替api单个操作
2020-08-30 00:23:36 +08:00
linwenxiang b646ce9335 在actions中添加权限 2020-08-30 00:22:21 +08:00
linwenxiang abc8b271c4 在actions中添加权限 2020-08-30 00:21:03 +08:00
linwenxiang 61451d149a Merge branch 'dev' of github.com:wenjianzhang/go-admin into table/dev 2020-08-29 23:29:42 +08:00
zhangwenjian 671291d024 update: used gormv2 to handle permission process 2020-08-29 23:25:53 +08:00
linwenxiang 562e61cf4f 使用action代替api单个操作
改写job
2020-08-29 22:22:52 +08:00
wenjianzhang d53ba4ed7a Merge pull request #221 from matchstalk/table/dev
修改model写法
2020-08-29 19:33:41 +08:00
linwenxiang 78417c2d33 修改model写法 2020-08-29 19:32:22 +08:00
wenjianzhang 5b9225ac3a Merge pull request #220 from matchstalk/table/dev
写法修改
2020-08-29 19:10:55 +08:00
linwenxiang 05b53c0710 支持主动加入路由 2020-08-29 19:08:55 +08:00
linwenxiang ca98fd873c 修改curd基本写法 2020-08-29 18:54:39 +08:00
zhangwenjian 55496078f0 update: set orm log 2020-08-29 17:52:59 +08:00
zhangwenjian c210d2301c update:update gen code 2020-08-29 17:31:28 +08:00
zhangwenjian 77499b50cc update:update gen code temp 2020-08-29 16:32:59 +08:00
zhangwenjian 3063386ec6 fix:create args bug 2020-08-29 16:31:56 +08:00
zhangwenjian 58c7b43759 Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-29 15:34:41 +08:00
zhangwenjian ce80857d5f feat:added dto 2020-08-29 15:33:44 +08:00
wenjianzhang a1e0b6cc5e Merge pull request #219 from matchstalk/integral-master
修改core仓库名称
2020-08-29 15:32:42 +08:00
linwenxiang 09e3c4f51d 修改环境变量名称 2020-08-29 15:30:55 +08:00
linwenxiang dc9bec2c22 添加debug 2020-08-29 15:19:01 +08:00
linwenxiang 8817f02038 修改core仓库名称 2020-08-29 15:15:03 +08:00
wenjianzhang 33b462217c Merge pull request #218 from matchstalk/table/dev
修改通用search语法结构
2020-08-28 21:13:15 +08:00
linwenxiang 486a77762c 修改通用search语法结构 2020-08-28 21:01:56 +08:00
zhangwenjian 87edf1099c feat:update search 2020-08-28 00:52:16 +08:00
zhangwenjian 1dffcb7199 Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-28 00:06:55 +08:00
zhangwenjian c69a7d9fd9 feat: format reference 2020-08-28 00:02:53 +08:00
wenjianzhang 5db1495985 Merge pull request #217 from matchstalk/table/dev
支持search
2020-08-27 22:33:57 +08:00
linwenxiang 65c4cfd684 支持search 2020-08-27 22:24:32 +08:00
linwenxiang de67c5dc46 支持search 2020-08-27 21:44:44 +08:00
wenjianzhang 6877e1324c Merge pull request #216 from matchstalk/table/dev
gorm升级到v2版本
2020-08-27 13:52:03 +08:00
linwenxiang 60c7496f7b gorm升级到v2版本 2020-08-27 11:17:15 +08:00
wenjianzhang 93cfe443d6 Merge pull request #214 from matchstalk/table/dev
分表基础算法
2020-08-26 22:32:11 +08:00
linwenxiang ffbacea589 导入表字段根据数据库字段排序,支持mysql 2020-08-26 22:27:31 +08:00
linwenxiang 7373536d36 修改注释 2020-08-25 23:10:09 +08:00
zhangwenjian 9f0ceda347 feat:added file info pid search 2020-08-25 23:07:34 +08:00
linwenxiang 22f5d39209 加入字符串字段分表算法,供分表业务参考 2020-08-25 23:06:10 +08:00
zhangwenjian d3376acc34 feat : update gen code & sql file 2020-08-25 22:34:29 +08:00
zhangwenjian aca9e19551 deat:added automigrate 2020-08-25 20:07:07 +08:00
zhangwenjian d27583421b feat:cms + gen code 2020-08-25 19:58:39 +08:00
zhangwenjian 7215f7c236 fix:role remove bug 2020-08-24 22:48:11 +08:00
zhangwenjian c2742e5f96 feat:update pagelist func 2020-08-24 22:41:20 +08:00
zhangwenjian 7b4ff44360 feat:added api template insert doc 2020-08-23 23:22:28 +08:00
zhangwenjian 87aa013cf7 fix:update dir 2020-08-23 22:58:11 +08:00
zhangwenjian 5a793b9e34 fix :dir insert bug 2020-08-23 22:55:57 +08:00
zhangwenjian 128e34ab04 feat: file manage 2020-08-23 20:49:06 +08:00
zhangwenjian b9d3483ba3 fix : role deletion problem (#208) 2020-08-21 15:07:03 +08:00
zhangwenjian 2c8c7688b4 feat: log output in ws mode 2020-08-20 23:31:46 +08:00
zhangwenjian a2f1721ee1 clear:remove no used code 2020-08-20 23:30:36 +08:00
zhangwenjian b26258c8ab feat: Task scheduling supports function with parameters
注意:参数资源sql;
ALTER TABLE `sys_job`
ADD COLUMN `args` varchar(255) NULL COMMENT '目标参数' AFTER `invoke_target`;
2020-08-20 06:30:44 +08:00
zhangwenjian 242902d2bc fix:Modify role permissions (#208) 2020-08-20 05:43:59 +08:00
zhangwenjian 2d11163ce5 upgrade:float64 formatting method 2020-08-20 05:33:37 +08:00
wenjianzhang c2f593de48 releases 1.1.5
releases 1.1.5
2020-08-18 16:49:54 +08:00
zhangwenjian 7140ff8a3a upgrade:ver 1.1.5 2020-08-18 16:45:50 +08:00
zhangwenjian cc6c333372 Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-18 00:03:28 +08:00
zhangwenjian 77bfc07826 fix: job nil bug 2020-08-18 00:03:02 +08:00
wenjianzhang a33b0d036a Merge pull request #200 from hqcchina/master
fix: Serve static javascript file with mime-type error
2020-08-16 19:35:00 +08:00
云景(Neil) 729154212f fix: Serve static javascript file mime-type error 2020-08-15 09:38:11 +08:00
wenjianzhang e345ffe9af releases 1.1.4
releases 1.1.4
2020-08-14 00:01:45 +08:00
zhangwenjian afb98f5e81 feat:update vue template & sql 2020-08-13 23:47:37 +08:00
zhangwenjian 826eef40a3 sql:update sql 2020-08-13 11:53:53 +08:00
zhangwenjian 589a9b7d9c version:update version No. 2020-08-13 11:33:35 +08:00
zhangwenjian 7889e49633 fix:Modify SQL and new business initialization bug 2020-08-13 11:21:54 +08:00
wenjianzhang ed238f0143 releases 1.1.3
releases 1.1.3
2020-08-12 21:31:57 +08:00
zhangwenjian 8ebcd772b2 fix:job list status invalid 2020-08-12 20:00:13 +08:00
zhangwenjian e9ef8a2d12 feat:update auto job 2020-08-12 19:59:04 +08:00
zhangwenjian 509709f3fb feat: update auto job (#191) 2020-08-12 18:21:04 +08:00
wenjianzhang 4dbacc7a16 Merge pull request #192 from matchstalk/master
修改job为接口类型
2020-08-11 23:36:32 +08:00
linwenxiang ee9f823bdd 修改job为接口类型 2020-08-11 23:30:20 +08:00
zhangwenjian 68eb8f1d51 feat : added func sql 2020-08-11 22:03:42 +08:00
zhangwenjian fcd91d3f18 Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-11 21:58:34 +08:00
zhangwenjian ec4bb6ee3a feat:auto job (#191) 2020-08-11 21:57:36 +08:00
wenjianzhang 938c865089 feat: Support cache + queue
支持缓存+队列
2020-08-10 21:12:52 +08:00
linwenxiang 2b794c03cc 支持缓存+队列,支持驱动:redis、memory 2020-08-10 20:57:17 +08:00
zhangwenjian 94aed14389 feat:added settings data 2020-08-09 02:49:31 +08:00
zhangwenjian 70e0141f64 feat: update system setting 2020-08-09 02:45:05 +08:00
zhangwenjian 6f8ad4efe4 feat : added auto task 2020-08-09 02:44:42 +08:00
zhangwenjian 5d88273910 feat:added job db & data 2020-08-09 02:43:49 +08:00
wenjianzhang 45bc92eb46 feat:added system baseInfo api
Dev
2020-08-08 09:26:35 +08:00
wenjianzhang 65fd96b481 feat: added dockerfile
添加Dockerfile
2020-08-08 09:24:48 +08:00
jalins e37aaee861 添加Dockerfile 2020-08-07 22:01:46 +08:00
wxb dca1919c69 新增系统设置接口 2020-08-07 10:51:01 +08:00
wxb 389d2970c0 新增系统设置接口 2020-08-07 10:22:52 +08:00
zhangwenjian c396f318ef Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-06 22:32:15 +08:00
zhangwenjian 54f48377f0 feat:added auto job 2020-08-06 22:30:57 +08:00
linwenxiang bdde879238 修改connect函数定义 2020-08-06 00:25:21 +08:00
linwenxiang d2639ccf22 修改方法名称 2020-08-06 00:20:04 +08:00
wenjianzhang e0c8c8de6a Merge pull request #186 from matchstalk/master
定义缓存和队列接口
2020-08-05 23:32:28 +08:00
linwenxiang b57af6c542 定义缓存和队列接口 2020-08-05 23:31:01 +08:00
linwenxiang b9d30eee88 定义缓存和队列接口 2020-08-05 23:30:05 +08:00
linwenxiang 18a2aeadaa 定义缓存和队列接口 2020-08-05 23:22:09 +08:00
wenjianzhang 2ed4869c8b Merge pull request #185 from matchstalk/master
开放三方依赖使用模式
2020-08-05 21:12:18 +08:00
linwenxiang 980d0977fc 开放对象,项目可以作为第三方依赖 2020-08-05 21:05:43 +08:00
wenjianzhang 055a68de7f Merge pull request #183 from wenjianzhang/dev
fix: Use NewSyncedEnforcer instead NewEnforcer to solve the thread safe
2020-08-03 14:25:59 +08:00
zhangwenjian 328f7d4f4f Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-03 14:25:30 +08:00
wenjianzhang 40492e56a0 ver : update version 1.1.2 2020-08-03 14:23:47 +08:00
zhangwenjian e709ee6209 Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-03 14:19:09 +08:00
zhangwenjian 80ecefb9f7 feat : added job 2020-08-03 14:18:33 +08:00
wenjianzhang e51b72d2e4 Merge pull request #182 from nodece/patch-1
fix: Use NewSyncedEnforcer instead NewEnforcer
2020-08-03 14:16:33 +08:00
Zixuan Liu cb9ae9b8ec fix: Use NewSyncedEnforcer instead NewEnforcer to solve the thread safe
Signed-off-by: Zixuan Liu <nodeces@gmail.com>
2020-08-03 14:12:07 +08:00
wenjianzhang 3316433f13 Merge pull request #178 from wenjianzhang/dev
1. 代码生成添加操作按钮确认提示;
2. 代码生成添加时间类型支持;
3. 代码生成添加时间控件、下拉控件、文本域的支持;
4. 修复代码生成无需权限的问题;
5. 修改了部分格式问题;
6. 默认开启log;
7. 修复了已知bug;
2020-08-01 14:52:29 +08:00
zhangwenjian c98f38cab4 gen :update import 2020-08-01 14:43:38 +08:00
zhangwenjian 88a458a003 gen : remove value-format 2020-08-01 14:07:06 +08:00
zhangwenjian 89fe63c915 gen : upgrade gen 2020-08-01 12:56:19 +08:00
zhangwenjian 80ef9e46ab Merge branch 'dev' of https://github.com/wenjianzhang/go-admin into dev 2020-08-01 11:11:44 +08:00
zhangwenjian 259b77dc08 fix : fix no auth code gen router 2020-08-01 11:08:40 +08:00
zhangwenjian 55b055b9a0 version : update version 2020-08-01 11:00:36 +08:00
zhangwenjian 309be41b05 feat : added version & config 2020-08-01 11:00:06 +08:00
zhangwenjian 1a580366fc feat : enabledbus status to true 2020-08-01 10:52:49 +08:00
zhangwenjian 93ac874312 feat : logger type error to fatal 2020-08-01 10:52:17 +08:00
zhangwenjian e5d22c78c8 doc : update readme 2020-08-01 10:50:19 +08:00
zhangwenjian 8d7a6ef3bb feat : update log format 2020-08-01 10:49:36 +08:00
zhangwenjian f050924234 sql : remove no used sql 2020-07-29 23:32:34 +08:00
zhangwenjian 061cb61aa7 remove : Prot default value removal 2020-07-29 23:31:51 +08:00
zhangwenjian 59e966ede6 feat : Adjust the length of the title field from the original 64 to 128 2020-07-29 15:08:44 +08:00
wenjianzhang 697ec9fcd5 doc : update readme 2020-07-28 00:07:41 +08:00
402 changed files with 38065 additions and 15129 deletions
+100
View File
@@ -0,0 +1,100 @@
---
name: new-business-module
description: Scaffold a new single-table CRUD business module end to end — migration, Actions-mode model/dto/router, and the sys_menu/sys_api/casbin seed data that makes it show up in the UI with working permissions. Use when the user wants to add a new business table/module to go-admin, not for cross-table or non-CRUD business logic.
---
# 新增业务模块
给一张新的业务表配齐"能跑、能看见、能授权"的完整闭环:迁移 → 后端代码 → 菜单与权限种子数据。
只适用于单表增删改查;跨表事务、外部调用、复杂校验等超出这个范围(见下方"何时不适用")。
开始前先读 `AGENTS.md`(分层边界、通用 Action 使用前提、命名规则)和 `app/demo/` 下的全部文件——
这是可编译、有测试、CI 会跑的参照物,本文与它冲突时以它为准。
## 何时不适用
业务超出单表 CRUD(跨表事务、外部服务调用、复杂校验)时,不要用这个 skill 硬套——
改成手写 Handler + Service,参照 `app/admin/apis/sys_post.go` 及其 Service,遵守
`AGENTS.md` 的分层约束(Api 不碰 Orm,Service 不碰 `gin.Context`,一律用 `e.Orm`)。
## 步骤
### 1. 确认表结构
表结构需符合命名规范:`sys_`/业务前缀 + 下划线(如 `tb_article`)。核对字段是否已有
`created_at`/`updated_at`/`deleted_at` 这类约定字段。
### 2. 写数据库迁移
放在 `cmd/migrate/migration/version/` 目录(**不是** `version-local/` —— 后者在
`.gitignore` 中,提交时会被忽略,`git status` 也看不到)。
- 文件名前 13 位是时间戳版本号
- 已执行过的迁移文件不可修改;需要修正时新增一个迁移
- 包名为 `version`
### 3. 生成 model / dto / router 三个文件(Actions 模式)
不要手写 Api 与 Service。使用 `common/actions` 的通用 Action,一个模块只需
model、dto、router 三个文件,完整写法照抄 `app/demo/` 的结构。
**关键正确性要求**(这三条是实际出问题最多的地方):
- Model 实现 `models.ActiveRecord`(`Generate` / `GetId` / `TableName`),
`TableName()` 必须显式声明——GORM 配置了 `SingularTable`,不会自动推导
- **`Generate()` 必须返回副本,不要就地返回**——Action 在并发请求间复用实例,
就地返回会导致请求之间串数据;这个问题单人测试时几乎不出现,上线后才暴露
- 完成后确认 `cmd/api/` 中已用 `_` 导入新包,否则路由不会被注册
### 4. 写菜单、接口与权限种子数据
这一步最容易被漏掉——代码能编译、接口能测通,但界面上看不到菜单、点了按钮说
没权限,往往就是漏了这一步。结构参照 `cmd/migrate/migration/version/1786700001000_demo_menu.go`
——它是可运行、幂等(用 `upsert`,重复跑不会报错)的真实例子。
:::danger
**但不要照抄它的 import。** 那个文件用的是 `cmd/migrate/migration/models`,
只因为它的版本号排在软删除转换(`1786700003000`)之前才是安全的。
**你新写的迁移版本号在转换之后,必须改用 `app/` 下的运行时模型**
(`app/admin/models.SysApi`、`SysMenu`),否则第一条 insert 就会
`NOT NULL constraint failed: sys_api.deleted_at`。
`TestPostConversionMigrationsAvoidFrozenSeedModels` 会拦住这个错误。
:::
一个模块要在界面上可用,需要四类数据,缺一样都不行:
| 表 | 作用 |
|---|---|
| `sys_api` | 后端路由登记,Casbin 据此判定权限 |
| `sys_menu` | 侧边栏菜单(目录用 `M`、菜单用 `C`、按钮用 `F`) |
| `sys_menu_api_rule` | 菜单与接口的多对多关联,角色保存时据此生成策略 |
| `casbin_rule` | 实际生效的权限策略(**不是** `sys_casbin_rule`,那张表的唯一索引在 MySQL 下会超长,不要迁移它) |
必须核对的两处一致性——**错了不会报错,只会在界面上表现为"看不到/点不动"**:
- `sys_menu.menu_name` 必须与前端组件的 `defineOptions({ name: 'XxxManage' })` 一致,
否则 `keep-alive` 缓存静默失效
- 按钮级 `sys_menu.permission`(格式 `模块:资源:操作`)必须与前端
`v-permisaction="['模块:资源:操作']"` 完全一致,否则按钮权限判断静默失效
### 5. 收尾检查
| 检查项 | 出错后果 |
| --- | --- |
| `Generate()` 是否返回副本 | 并发请求之间串数据 |
| 是否使用 `e.Orm` 而非全局 DB | 多租户下拿到错误的数据库连接 |
| `TableName()` 是否显式声明 | GORM 不会自动推导 |
| 迁移文件是否放在 `version/` | 放进 `version-local/` 会被忽略,别人拉代码看不到 |
| `sys_menu.menu_name` 是否与前端组件 `name` 一致 | keep-alive 缓存静默失效 |
| `sys_menu.permission` 是否与前端 `v-permisaction` 一致 | 按钮权限静默失效 |
跑一遍 `go run -tags sqlite3 . migrate -c config/settings.sqlite.yml` 验证迁移可执行,
再 `go run -tags sqlite3 . server -c config/settings.sqlite.yml` 启动服务,用 admin
账号登录确认新菜单和按钮权限都出现了。
如果前端页面还没生成,下一步用 go-admin-ui 仓库里的 `new-list-page` skill——两边靠
`sys_menu.permission` / `v-permisaction` 这个字符串对齐。
> 不要把 `config/settings.yml` 的真实内容贴给 AI 工具——`database.source` 含数据库
> 账号密码,`jwt.secret` 泄露后可被用来伪造任意用户的 token。
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: 🆕 Create new issue
url: http://new-issue.go-admin.dev
about: The issue which is not created via http://new-issue.go-admin.dev will be closed immediately.
- name: 🆕 创建一个新 Issue
url: http://new-issue.go-admin.dev
about: 不是用 http://new-issue.go-admin.dev 创建的 issue 会被机器人自动关闭。
+66
View File
@@ -0,0 +1,66 @@
<!--
First of all, thank you for your contribution! 😄
For requesting to pull a new feature or bugfix, please send it from a feature/bugfix branch based on the `master` branch.
Before submitting your pull request, please make sure the checklist below is confirmed.
Your pull requests will be merged after one of the collaborators approve.
Thank you!
-->
[[中文版模板 / Chinese template](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
### 🤔 This is a ...
- [ ] New feature
- [ ] Bug fix
- [ ] Site / documentation update
- [ ] Demo update
- [ ] Component style update
- [ ] TypeScript definition update
- [ ] Bundle size optimization
- [ ] Performance optimization
- [ ] Enhancement feature
- [ ] Internationalization
- [ ] Refactoring
- [ ] Code style optimization
- [ ] Test Case
- [ ] Branch merge
- [ ] Other (about what?)
### 🔗 Related issue link
<!--
1. Put the related issue or discussion links here.
-->
### 💡 Background and solution
<!--
1. Describe the problem and the scenario.
2. GIF or snapshot should be provided if includes UI/interactive modification.
3. How to fix the problem, and list the final API implementation and usage sample if that is a new feature.
-->
### 📝 Changelog
<!--
Describe changes from the user side, and list all potential break changes or other risks.
--->
| Language | Changelog |
| ---------- | --------- |
| 🇺🇸 English | |
| 🇨🇳 Chinese | |
### ☑️ Self-Check before Merge
⚠️ Please check all items below before review. ⚠️
- [ ] Doc is updated/provided or not needed
- [ ] Demo is updated/provided or not needed
- [ ] TypeScript's definition is updated/provided or not needed
- [ ] Changelog is provided or not needed
+61
View File
@@ -0,0 +1,61 @@
<!--
首先,感谢你的贡献!😄
新特性请提交至 feature 分支,其余可提交至 master 分支。
在维护者审核通过后会合并。
请确保填写以下 pull request 的信息,谢谢!~
-->
[[English Template / 英文模板](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE.md)]
### 🤔 这个变动的性质是?
- [ ] 新特性提交
- [ ] 日常 bug 修复
- [ ] 站点、文档改进
- [ ] 演示代码改进
- [ ] 组件样式/交互改进
- [ ] TypeScript 定义更新
- [ ] 包体积优化
- [ ] 性能优化
- [ ] 功能增强
- [ ] 国际化改进
- [ ] 重构
- [ ] 代码风格优化
- [ ] 测试用例
- [ ] 分支合并
- [ ] 其他改动(是关于什么的改动?)
### 🔗 相关 Issue
<!--
1. 描述相关需求的来源,如相关的 issue 讨论链接。
-->
### 💡 需求背景和解决方案
<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->
### 📝 更新日志
<!--
从用户角度描述具体变化,以及可能的 breaking change 和其他风险。
-->
| 语言 | 更新描述 |
| ------- | -------- |
| 🇺🇸 英文 | |
| 🇨🇳 中文 | |
### ☑️ 请求合并前的自查清单
⚠️ 请自检并全部**勾选全部选项**。⚠️
- [ ] 文档已补充或无须补充
- [ ] 代码演示已提供或无须提供
- [ ] TypeScript 定义已补充或无须补充
- [ ] Changelog 已提供或无须提供
+146
View File
@@ -0,0 +1,146 @@
name: Build
# Documentation-only changes skip this workflow entirely.
#
# A push to master here does not just build - it pushes an image, runs the
# migrations and restarts the demo container, so the site takes a short outage.
# Paying that for a README edit is waste at best; at worst a deploy fails for a
# reason unrelated to anything in the change. Code coverage is unaffected,
# because go.yml still builds every push and pull request.
on:
push:
branches: [ master ]
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE*'
- '.github/ISSUE_TEMPLATE/**'
pull_request:
branches: [ master ]
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE*'
- '.github/ISSUE_TEMPLATE/**'
# One deploy at a time. Two merges seconds apart raced here: both runs did
# docker rm -f then docker run, the second removed the container the first had
# just created, and the first's docker run then failed on a name conflict -
# leaving the demo on the older image with a red deploy.
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
env:
IMAGE_NAME: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api # 镜像名称
TAG: ${{ github.sha }}
IMAGE_NAME_TAG: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api:${{ github.sha }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.26.5
- name: Tidy
run: go mod tidy
- name: Build
run: env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags "sqlite3,json1" --ldflags "-extldflags -static" -o main .
# 以下推镜像与重启步骤仅在 master 收到 push 时执行。
# pull_request 事件同样会触发本工作流,若不加限制,任何指向 master 的
# PR 一经创建就会把 PR 分支的镜像推上仓库,并直接重启线上 API 服务,
# 且发生在合并之前。构建与编译校验不受影响,PR 仍会执行。
- name: Build the Docker image and push
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
run: |
docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
echo "************ docker login end"
docker build -t go-admin-api:latest .
echo "************ docker build end"
docker tag go-admin-api ${{ env.IMAGE_NAME_TAG }}
echo "************ docker tag end"
docker images
echo "************ docker images end"
docker push ${{ env.IMAGE_NAME_TAG }} # 推送
echo "************ docker push end"
- name: Restart server # 第五步,重启服务
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
env:
GITHUB_SHA_X: ${GITHUB_SHA}
with:
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
# 重启的脚本,根据自身情况做相应改动,一般要做的是migrate数据库以及重启服务器
#
# 配置从宿主机挂载,不使用镜像里的那份:演示站连的是托管数据库,
# 而 config/settings.demo.yml 会随仓库公开、也会打进镜像,凭据不能写在那里。
# 镜像里那份保持 sqlite,供 clone 仓库的人开箱即用。
#
# 路径本身走 secret:它不是凭据,但本仓库公开,没有理由把服务器的
# 目录结构一并公布。DEMO_CONFIG_PATH 指向宿主机上那份配置。
#
# 顺序是有意的:迁移先跑,跑不过就保持现有版本不动;
# 旧容器改名保留而不是删除,新容器不健康时能原样恢复。
# 健康检查两条都要过——HTTP 活着不代表数据库通了。
script: |
set -u
CFG="${{ secrets.DEMO_CONFIG_PATH }}"
IMG="${{ env.IMAGE_NAME_TAG }}"
NAME=go-admin-api
PREV="$NAME-prev"
test -f "$CFG" || { echo "宿主机配置缺失,中止部署"; exit 1; }
sudo docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
sudo docker pull "$IMG" || { echo "拉取镜像失败,中止部署"; exit 1; }
# 迁移用新镜像跑。失败时线上仍是旧版本配旧 schema,是自洽的;
# 硬切过去才会得到代码与表对不上的服务。
if ! sudo docker run --rm -v "$CFG":/config/settings.yml:ro "$IMG" \
/main migrate -c /config/settings.yml; then
echo "迁移失败,保持现有版本"; exit 1
fi
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$NAME"; then
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
sudo docker rename "$NAME" "$PREV"
sudo docker stop "$PREV" >/dev/null
fi
sudo docker run -d -p 8000:8000 \
-v "$CFG":/config/settings.yml:ro \
--name "$NAME" "$IMG"
ok=0
for i in $(seq 1 20); do
sleep 3
code=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://127.0.0.1:8000/api/v1/captcha 2>/dev/null || true)
if [ "$code" = "200" ] && sudo docker logs "$NAME" 2>&1 | grep -q 'connect success'; then
ok=1; echo "健康检查通过(第 $i 次探测)"; break
fi
done
if [ "$ok" = "1" ]; then
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
else
echo "健康检查失败,回滚到上一版本"
sudo docker logs --tail 40 "$NAME" 2>&1 || true
sudo docker rm -f "$NAME" >/dev/null 2>&1 || true
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$PREV"; then
sudo docker rename "$PREV" "$NAME"
sudo docker start "$NAME" >/dev/null
echo "已恢复"
fi
exit 1
fi
+51
View File
@@ -0,0 +1,51 @@
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
+59 -12
View File
@@ -2,9 +2,13 @@ name: build
on:
push:
branches: [ master ]
branches: [ master, dev ]
tags: [ 'v*', '[0-9]*' ]
pull_request:
branches: [ master ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
@@ -13,22 +17,65 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
- name: Set up Go 1.26
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.13
go-version: 1.26.5
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
run: go mod tidy
# go build does not compile _test.go, so building alone never ran a single
# test. This is the only workflow that fires on every push and pull request,
# which makes it the one place a test gate belongs.
- name: Test
run: make test
- name: Build
run: go build -v .
run: make build
# Fails the build on the silent-failure classes listed in
# tools/checksilent, one of which is the contract boundary: nothing under
# common/ may import app/. A boundary that is only written down erodes; this
# is what keeps it true.
- name: Silent-failure checks
run: make checksilent
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
if: startsWith(github.ref, 'refs/tags/')
- name: Log in to the Container registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
if: startsWith(github.ref, 'refs/tags/')
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
if: startsWith(github.ref, 'refs/tags/')
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=auto
tags: |
type=schedule
type=ref,event=tag
type=sha,prefix=,format=long,enable=true,priority=100
- name: Build and push Docker image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
if: startsWith(github.ref, 'refs/tags/')
with:
context: .
file: scripts/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+33 -3
View File
@@ -1,12 +1,42 @@
.idea
.vscode
.DS_Store
static/uploadfile
main.exe
*.exe
go-admin
go-admin.exe
# `go build ./tools/checksilent` drops the binary here, next to the one for the
# server. Anchored with a leading slash: unanchored, the same pattern matches
# tools/checksilent/ as well and the tool's own source never gets committed.
/checksilent
temp/
!temp
vendor
config/settings.dev.yml
go-admin
middleware/demo.go
config/settings.dev.b.yml
config/settings.dev.*.yml
config/settings.dev.*.yml.log
temp/logs
config/settings.dev.yml.log
config/settings.b.dev.yml
cmd/migrate/migration/version-local/*
!cmd/migrate/migration/version-local/doc.go
config/settings.deva.yml
go-admin-server
CLAUDE.md
# Everything under .claude is private by default. Skills meant for people using
# go-admin are re-included one directory at a time, so a personal one dropped in
# here is never committed by accident.
.claude/*
!.claude/skills/
.claude/skills/*
!.claude/skills/new-business-module/
config/settings.local.dev.yml
# Go workspace files. They exist to point this module at a local checkout of
# go-admin-core while the two are developed together, which is a private
# arrangement between one machine's directories - committing one would break
# the build for everyone else.
go.work
go.work.sum
+265
View File
@@ -0,0 +1,265 @@
# AGENTS.md — go-admin 后端
> 给 AI 编码工具与新贡献者的约定。**只写"不遵守就会出错"的规则**;技术栈版本以
> `go.mod` 为准,命令以 `Makefile` 为准,此处不复述,避免与代码脱节。
>
> 标准 CRUD 模块的完整写法见 **`app/demo/`** —— 那是可编译、有测试、CI 会跑的参照物。
> 本文与它冲突时,以 `app/demo/` 为准。
## 分层
```
Router → Api → Service → Model
路由注册 参数绑定 业务逻辑 GORM 结构体
中间件链 调用 Service 操作数据库 TableName()
```
对应目录:`app/{模块}/router|apis|service|models`,DTO 位于 `service/dto`。
**不可跨层**:Api 不直接操作 `Orm`,Service 不接触 `gin.Context`。
## 优先使用通用 Action
单表 CRUD **不要手写 Handler 与 Service**。`common/actions` 提供的五个
Action 已覆盖参数绑定、数据权限过滤、操作人注入、分页与错误响应:
```go
r := v1.Group("/demo-product").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
m := &models.DemoProduct{}
r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} {
list := make([]models.DemoProduct, 0); return &list
}))
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} {
return &models.DemoProduct{}
}))
r.POST("", actions.CreateAction(new(dto.DemoProductControl)))
r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.DemoProductControl)))
r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.DemoProductById)))
}
```
这样一个模块只需 **model + dto + router** 三个文件,完整示例见 `app/demo/`。
使用通用 Action 的前提:
- Model 实现 `models.ActiveRecord`(`Generate` / `GetId` / `TableName`)
- 列表 DTO 实现 `dto.Index`,增改删 DTO 实现 `dto.Control`
- **所有 `Generate()` 必须返回副本** —— Action 在并发请求间复用实例,
就地返回会串数据(`app/demo` 的测试锁定了这一点)
- 详情/删除 DTO 内嵌 `dto.ObjectById` 即可继承 `Bind` 与 `GetId`,无需重写
仅当业务超出单表 CRUD(跨表事务、外部调用、复杂校验)时才自行编写 Handler
与 Service,写法见下。
## Api 层(仅在通用 Action 不适用时)
结构体嵌入 `api.Api`,链式初始化后**必须检查 `Errors`**:
```go
func (e SysPost) GetPage(c *gin.Context) {
s := service.SysPost{}
req := dto.SysPostPageReq{}
err := e.MakeContext(c).MakeOrm().Bind(&req, binding.Form).MakeService(&s.Service).Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// ... 调用 s.GetPage(...)
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
```
响应一律走 `e.OK` / `e.PageOK` / `e.Error`,不要自行 `c.JSON`。
## Service 层(仅在通用 Action 不适用时)
结构体嵌入 `service.Service`(持有 `Orm` 与 `Log`)。查询通过 Scopes 组合:
```go
err = e.Orm.Model(&data).Scopes(
cDto.MakeCondition(c.GetNeedSearch()), // 由 search tag 生成 WHERE
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p), // 数据权限,列表/详情必须带
).Find(list).Limit(-1).Offset(-1).Count(count).Error
```
**遗漏 `actions.Permission` 会使数据权限配置静默失效** —— 这是最容易出的错。
错误一律 `return err` 向上传递,日志用 `e.Log.Errorf`,不使用 `panic`。
## DTO
搜索条件由 tag 声明,`MakeCondition` 据此拼 SQL:
```go
type SysPostPageReq struct {
dto.Pagination `search:"-"`
PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post"`
}
func (m *SysPostPageReq) GetNeedSearch() interface{} { return *m }
```
`type` 可选:`exact` `iexact` `contains` `gt` `gte` `lt` `lte` `order` `left`(联表)。
## Model
```go
type SysPost struct {
PostId int `gorm:"primaryKey;autoIncrement" json:"postId"`
// ... 业务字段
models.ControlBy // CreateBy / UpdateBy
models.ModelTime // CreatedAt / UpdatedAt / DeletedAt
}
func (SysPost) TableName() string { return "sys_post" }
```
`TableName()` 必须显式声明(GORM 配置了 `SingularTable`,不会自动推导复数)。
## 公共契约面
第三方应用(`app/` 下的业务模块)可以稳定依赖哪些包、路由与迁移怎么注册、
哪些约束是硬的,见 `docs/contract.md`。
两条与主仓贡献者直接相关的:
- **`common/`、`core/` 不得 import `app/`** —— `make checksilent` 在 CI 里守着,违反即红。
- **从 core 契约包声明出来的类型必须写成别名**(`type X = pkg.Y`,不是 `type X pkg.Y`)
—— `contract-shim-alias` 检查守着。defined type 会丢掉整个方法集,
而且**不一定在本仓编译失败**,理由见 `docs/contract.md` 末节。
- **注册类 API(`AppRouters` / `sdk.Runtime.SetAppRouters` / `migration.ForApp`)
必须在 `runStartupHooks()` 之前调用完** —— `init()` 是最省事的位置,
但约束的是**顺序**,不是写在哪个函数里;晚到的注册会被丢弃并只记一条 ERROR。
## 路由注册
通过 `init()` 自注册,不在中心文件手工添加:
```go
func init() { routerCheckRole = append(routerCheckRole, registerSysPostRouter) }
func registerSysPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysPost{}
r := v1.Group("/post").
Use(authMiddleware.MiddlewareFunc()).
Use(middleware.AuthCheckRole()). // Casbin 鉴权
Use(actions.PermissionAction()) // 注入数据权限
{ r.GET("", api.GetPage); r.POST("", api.Insert); /* ... */ }
}
```
新增路由文件后,需确认 `cmd/api/` 中已用 `_` 导入该包。
## 命名
| 对象 | 规则 | 示例 |
|---|---|---|
| 数据表 | `sys_` 前缀 + 下划线 | `sys_post` |
| API 路径 | `/api/v1/` + kebab-case | `/api/v1/sys-user` |
| DTO | `{Model}{Action}Req` | `SysPostPageReq` |
| 权限标识 | `模块:资源:操作` | `admin:sysPost:add` |
权限标识需与前端 `v-permisaction` 一致,并写入 `sys_menu` 种子数据——完整可运行的
参照见 `cmd/migrate/migration/version/1786700001000_demo_menu.go`(sys_api /
sys_menu / sys_menu_api_rule / casbin_rule 四张表如何配齐,用的是幂等 upsert,
可以直接照抄结构)。
## Swagger
Handler 必须带完整注解,`go generate` 会据此生成文档:
```go
// @Summary 岗位列表
// @Tags 岗位
// @Success 200 {object} response.Response
// @Router /api/v1/post [get]
// @Security Bearer
```
## 本地运行
**配置 `driver: sqlite3` 时必须带构建标签**,否则启动即 panic:
```bash
go run -tags sqlite3 . migrate -c config/settings.sqlite.yml
go run -tags sqlite3 . server -c config/settings.sqlite.yml
```
原因:`common/database/open.go` 带 `//go:build !sqlite3`,不加标签时编进的是
不含 sqlite3 的版本,`opens["sqlite3"]` 为 nil,调用时在 nil 函数上崩溃。
报错信息不会提到构建标签,容易误判成环境损坏。MySQL / PostgreSQL 无此问题。
对应 `Makefile` 的 `build-sqlite` 目标。
## 数据库迁移
文件名前 13 位为毫秒时间戳版本号,不合规的名字会在启动时 panic 并报出该文件名。
**已执行过的迁移文件不可修改** ——
`sys_migration` 表按版本号去重,改动不会重跑,只能新增一个迁移来修正。
放哪个目录取决于身份:
| 目录 | 用途 | 是否入库 |
|---|---|---|
| `version/` | 框架自带迁移,随仓库分发给所有使用者 | 是 |
| `version-local/` | 使用者自己项目的迁移 | 否(已在 `.gitignore`) |
**向本仓库提交迁移必须放 `version/`** —— 放进 `version-local/` 会被忽略掉,
`git status` 看不到,PR 里也不会出现。两个目录的包名分别是 `version` 与
`version_local`(后者与目录名不一致,因为标识符不能含连字符)。
### 写种子数据用哪个 models 包
`1786700003000` 之后新增的迁移,**种子数据要用 `app/` 下的运行时模型**
(如 `app/admin/models.SysApi`、`SysMenu`),**不要用 `cmd/migrate/migration/models`**。
后者的 `ModelTime` 声明的是可空的 `gorm.DeletedAt`,这对它之前的迁移是对的(那正是
当时列的形状),转换之后就不再成立,两个方向都会出问题:
- **写**:往 NOT NULL 列里塞 NULL,第一条 insert 就 `NOT NULL constraint failed`
- **读**:GORM 拼 `WHERE deleted_at IS NULL`,而活跃行存的是 `0`,静默查不到——
照抄 `demo_menu.go` 的授权段落会因此跳过授权,菜单建好、权限没授、迁移仍记为成功
干净库跑不出这个问题,今天所有用该包的迁移都排在转换之前。完整推导见
`schema_coverage_test.go` 里 `TestPostConversionMigrationsAvoidFrozenSeedModels`
的注释,那个测试也守着这条边界。
## 静默失败校验
`make checksilent` 检查七类**不报错、不记日志、行为悄悄变得不对**的问题,
CI 会跑,命中 ERROR 即失败:
| 检查 | 级别 | 静默后果 |
|---|---|---|
| `modeltime-mix` | ERROR | 两个 `ModelTime` 混用,整张表查不到数据 |
| `menu-sort-overflow` | ERROR | 菜单 `sort` 超 127,MySQL tinyint 拒绝写入,迁移中断 |
| `config-value-truncation` | ERROR | `sys_config.config_value` 超 255 字符被静默截断 |
| `menu-id-collision` | ERROR | 两个模块硬编码同一菜单 ID,互相覆盖 |
| `contract-import-boundary` | ERROR | 契约包 import `app/`,应用无法独立编译 |
| `contract-shim-alias` | ERROR | 契约薄壳写成 defined type 而非别名,方法集丢失,本仓可能照常编译、第三方应用编译不过 |
| `menu-name-mismatch` | WARN | 菜单名与前端组件 `name` 不一致,keep-alive 缓存静默失效 |
最后一条要跨仓库比对,只能做正则启发式,因此是 WARN,**不影响退出码**,
且默认跳过;要跑它得指定前端目录:
```bash
make checksilent UI_DIR=../go-admin-ui/src
```
升级门槛:连续 2 个发版周期零误报后转为 ERROR。
## 提交规范
格式 `type+emoji: 描述`:
`feat✨` `fix🐛` `style💄` `docs📝` `perf👌` `test✅` `refactor🎨` `chore🔧`
一个提交只做一件事。改动跨越多个语义时拆分提交,不要混在一起。
## 红线
- 不使用全局 DB 变量,一律用 `e.Orm`(来自请求上下文,多租户依赖它)
- 不在 Service 中引用 `gin.Context`
- 生产部署前确认 `mode: prod` 且已修改 `jwt.secret`(dev 模式下 token 几乎不过期)
- 不提交 `config/settings.yml` 中的真实凭据
+18
View File
@@ -0,0 +1,18 @@
FROM alpine
# ENV GOPROXY https://goproxy.cn/
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update --no-cache
RUN apk add --update gcc g++ libc6-compat
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache tzdata
ENV TZ Asia/Shanghai
COPY ./main /main
COPY ./config/settings.demo.yml /config/settings.yml
COPY ./go-admin-db.db /go-admin-db.db
EXPOSE 8000
RUN chmod +x /main
CMD ["/main","server","-c", "/config/settings.yml"]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 wenjianzhang
Copyright (c) 2026 go-admin-team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+63 -5
View File
@@ -2,13 +2,71 @@ PROJECT:=go-admin
.PHONY: build
build:
CGO_ENABLED=0 go build -o go-admin main.go
CGO_ENABLED=0 go build -ldflags="-w -s" -a -installsuffix "" -o go-admin .
# make build-linux
build-linux:
@docker build -t go-admin:latest .
@echo "build successful"
build-sqlite:
go build -tags sqlite3 -o go-admin main.go
#.PHONY: test
#test:
# go test -v ./... -cover
go build -tags sqlite3 -ldflags="-w -s" -a -installsuffix -o go-admin .
# make run
run:
# delete go-admin-api container
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
# 启动方法一 run go-admin-api container docker-compose 启动方式
# 进入到项目根目录 执行 make run 命令
@docker-compose up -d
# 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际绝对路径
#@docker run --name=go-admin -p 8000:8000 -v /home/code/go/src/go-admin/go-admin/config:/go-admin-api/config -v /home/code/go/src/go-admin/go-admin-api/static:/go-admin/static -v /home/code/go/src/go-admin/go-admin/temp:/go-admin-api/temp -d --restart=always go-admin:latest
@echo "go-admin service is running..."
# delete Tag=<none> 的镜像
@docker image prune -f
@docker ps -a | grep "go-admin"
stop:
# delete go-admin-api container
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker-compose down; fi
#@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
#@echo "go-admin stop success"
# -race is worth the extra minute here: common/actions reuses model instances
# across concurrent requests, so a Generate() that returns in place instead of
# a copy leaks data between them - and that is invisible to a single-threaded
# test run.
.PHONY: test
test:
go test -race -cover ./...
# Reports the failures that do not announce themselves - see
# tools/checksilent. Exits non-zero on an ERROR; the one WARN-level check
# prints and does not fail the build.
#
# Pass UI_DIR to enable the cross-repository menu-name check, which is skipped
# without it: make checksilent UI_DIR=../go-admin-ui/src
.PHONY: checksilent
checksilent:
ifdef UI_DIR
go run ./tools/checksilent -ui-dir $(UI_DIR)
else
go run ./tools/checksilent
endif
#.PHONY: docker
#docker:
# docker build . -t go-admin:latest
# make deploy
deploy:
#@git checkout master
#@git pull origin master
make build-linux
make run
+352
View File
@@ -0,0 +1,352 @@
# go-admin
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
[![Build Status](https://github.com/go-admin-team/go-admin/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/go-admin-team/go-admin)
[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases)
[![License](https://img.shields.io/github/license/go-admin-team/go-admin.svg)](https://github.com/go-admin-team/go-admin)
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | 简体中文 | [繁體中文](https://github.com/go-admin-team/go-admin/blob/master/README.zh-TW.md) | [日本語](https://github.com/go-admin-team/go-admin/blob/master/README.ja-JP.md)
基于Gin + Vue + Element UI OR Arco Design OR Ant Design的前后端分离权限管理系统,系统初始化极度简单,只需要配置文件中,修改数据库连接,系统支持多指令操作,迁移指令可以让初始化数据库信息变得更简单,服务指令可以很简单的启动api服务
[在线文档](https://www.go-admin.pro)
[前端项目](https://github.com/go-admin-team/go-admin-ui)
[视频教程](https://space.bilibili.com/565616721/channel/detail?cid=125737)
## 🎬 在线体验
Element Plus vue3 体验:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
antd 体验(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
## ✨ 特性
- 遵循 RESTful API 设计规范
- 基于 GIN WEB API 框架,提供了丰富的中间件支持(用户认证、跨域、访问日志、追踪ID等)
- 基于Casbin的 RBAC 访问控制模型
- JWT 认证
- 支持 Swagger 文档(基于swaggo)
- 基于 GORM 的数据库存储,可扩展多种类型数据库
- 配置文件简单的模型映射,快速能够得到想要的配置
- 代码生成工具
- 表单构建工具
- 多指令模式
- 多租户的支持
- TODO: 单元测试
## 🎁 内置
1. 多租户:系统默认支持多租户,按库分离,一个库一个租户。
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识,接口权限等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
9. 登录日志:系统登录日志记录查询包含登录异常。
1. 接口文档:根据业务代码自动生成相关的api接口文档。
1. 代码生成:根据数据表结构生成对应的增删改查相对应业务,全程可视化操作,让基本业务可以零代码实现。
1. 表单构建:自定义页面样式,拖拉拽实现页面布局。
1. 服务监控:查看一些服务器的基本信息。
1. 内容管理:demo功能,下设分类管理、内容管理。可以参考使用方便快速入门。
1. 定时任务:自动化任务,目前支持接口调用和函数调用。
## 准备工作
你需要在本地安装 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
同时配套了系列教程包含视频和文档,如何从下载完成到熟练使用,强烈建议大家先看完这些教程再来实践本项目!!!
### 轻松实现go-admin写出第一个应用 - 文档教程
[步骤一 - 基础内容介绍](https://www.go-admin.pro/guide/intro/tutorial01.html)
[步骤二 - 实际应用 - 编写增删改查](https://www.go-admin.pro/guide/intro/tutorial02.html)
### 手把手教你从入门到放弃 - 视频教程
[如何启动go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
[使用生成工具轻松实现业务](https://www.bilibili.com/video/BV1Dg4y1i79D)
[v1.1.0版本代码生成工具-释放双手](https://www.bilibili.com/video/BV1N54y1i71P) [进阶]
[多命令启动方式讲解以及IDE配置](https://www.bilibili.com/video/BV1Fg4y1q7ph)
[go-admin菜单的配置说明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看]
[如何配置菜单信息以及接口信息](https://www.bilibili.com/video/BV1zv411B7nG) [必看]
[go-admin权限配置使用说明](https://www.bilibili.com/video/BV1rt4y197d3) [必看]
[go-admin数据权限使用说明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看]
**如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr ,视频教程和文档持续更新中**
## 📦 本地开发
### 环境要求
go 1.26.5
node版本: v22+(推荐 v24 LTS)
包管理器: pnpm v9+(UI 项目使用 pnpm)
### 开发目录创建
```bash
# 创建开发目录
mkdir goadmin
cd goadmin
```
### 获取代码
> 重点注意:两个项目必须放在同一文件夹下;
```bash
# 获取后端代码
git clone https://github.com/go-admin-team/go-admin.git
# 获取前端代码
git clone https://github.com/go-admin-team/go-admin-ui.git
```
### 启动说明
#### 服务端启动说明
```bash
# 进入 go-admin 后端项目
cd ./go-admin
# 更新整理依赖
go mod tidy
# 编译项目
go build
# 修改配置
# 文件路径 go-admin/config/settings.yml
vi ./config/settings.yml
# 1. 配置文件中修改数据库信息
# 注意: settings.database 下对应的配置数据
# 2. 确认log路径
```
⚠️注意 在windows环境如果没有安装中CGO,会出现这个问题;
```bash
E:\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
```
or
```bash
D:\Code\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
```
[解决cgo问题进入](https://www.go-admin.pro/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
#### 初始化数据库,以及服务启动
``` bash
# 首次配置需要初始化数据库资源信息
# macOS or linux 下使用
$ ./go-admin migrate -c config/settings.dev.yml
# ⚠️注意:windows 下使用
$ go-admin.exe migrate -c config/settings.dev.yml
# 启动项目,也可以用IDE进行调试
# macOS or linux 下使用
$ ./go-admin server -c config/settings.yml
# ⚠️注意:windows 下使用
$ go-admin.exe server -c config/settings.yml
```
#### sys_api 表的数据如何添加
在项目启动时,使用`-a true` 系统会自动添加缺少的接口数据
```bash
./go-admin server -c config/settings.yml -a true
```
#### 使用docker 编译启动
```shell
# 编译镜像
docker build -t go-admin .
# 启动容器,第一个go-admin是容器名字,第二个go-admin是镜像名称
# -v 映射配置文件 本地路径:容器路径
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
```
#### 文档生成
```bash
go generate
```
#### 交叉编译
```bash
# windows
env GOOS=windows GOARCH=amd64 go build main.go
# or
# linux
env GOOS=linux GOARCH=amd64 go build main.go
```
### UI交互端启动说明
```bash
# 安装 pnpm(若未安装)
npm install -g pnpm
# 安装依赖
pnpm install
# 国内网络可指定镜像源加速
pnpm install --registry=https://registry.npmmirror.com
# 启动服务
pnpm dev
```
## 📨 互动
<table>
<tr>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
</tr>
<tr>
<td>微信</td>
<td>公众号🔥🔥🔥</td>
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
<td>哔哩哔哩🔥🔥🔥</td>
</tr>
</table>
## 💎 贡献者
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
## JetBrains 开源证书支持
`go-admin` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
## 🤝 特别感谢
1. [ant-design](https://github.com/ant-design/ant-design)
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
2. [arco-design](https://github.com/arco-design/arco-design)
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
4. [gin](https://github.com/gin-gonic/gin)
5. [casbin](https://github.com/casbin/casbin)
6. [spf13/viper](https://github.com/spf13/viper)
7. [gorm](https://github.com/go-gorm/gorm)
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
9. [golang-jwt](https://github.com/golang-jwt/jwt)
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
12. [form-generator](https://github.com/JakHuang/form-generator)
## 🤟 打赏
> 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
## 🤝 链接
- [mss-boot-io](https://docs.mss-boot-io.top/)
## 🔑 License
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
Copyright (c) 2026 wenjianzhang
-194
View File
@@ -1,194 +0,0 @@
# go-admin
![build](https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg) ![license](https://img.shields.io/github/license/mashape/apistatus.svg)
English | [简体中文](./README.zh-CN.md)
##### Gin + Vue + Element UI based scaffolding for front and back separation management system
## ✨ Feature
- Follow RESTful API design specifications
- Provides rich middleware support based on GIN WEB API framework (user authentication, cross domain, access log, tracking ID, etc.)
- Casbin-based RBAC access control model
- JWT certification
- Support Swagger documentation (based on swaggo)
- GORM-based database storage that can expand many types of databases
- Simple model mapping of configuration files to quickly get the desired configuration
- TODO: unit test
## 🎁 Built-in functions
1. User management: The user is the system operator. This function mainly completes the system user configuration.
2. Department management: configure the system organization (company, department, group), and display the tree structure to support data permissions.
3. Post management: Configure system users to hold positions.
4. Menu management: configure system menus, operation permissions, button permission labels, etc.
5. Role management: role menu permissions assignment, setting roles to divide data range permissions by organization.
6. Dictionary management: to maintain some fixed data often used in the system.
7. Parameter management: Dynamically configure common parameters for the system.
8. Operation log: system normal operation log record and query; system exception information log record and query.
9. Login log: The system login log record query contains login exceptions.
10. System interface: Automatically generate related api interface documents according to business code.
## Configuration details
1. Configuration file description
```yml
settings:
application:
# Project launch environment
env: dev
# When env: demo, prompts for request operations other than GET
envmsg: "谢谢您的参与,但为了大家更好的体验,所以本次提交就算了吧!"
# Host IP or domain name, default 0.0.0.0
host: 0.0.0.0
# Whether to initialize the database structure and basic data; true: required; false: not required
isinit: false
# JWT encrypted string
jwtsecret: 123abc
# log storage path
logpath: temp/logs/log.log
# application name
name: go-admin
# application port
port: 8000
readtimeout: 1
writertimeout: 2
database:
# database name
database: dbname
# database type
dbtype: mysql
# database host
host: 127.0.0.1
# database password
password: password
# database port
port: 3306
# database username
username: root
redis:
# redis addresss
addr: 0.0.0.0:6379
# db
db: 0
# password
password: password
# read timeout
readtimeout: 50
```
2. file path go-admin/config/settings.yml
## 📦 evelopment
First start instructions
```bash
# Get the code
git clone https://github.com/wenjianzhang/go-admin.git
# Enter working path
cd ./go-admin
# Build the project
go build
# Change setting
vi ./config/setting.yml (Note: Change isinit and database connection)
# 1. Database information in the configuration file
# Note: the corresponding configuration data under settings.database
# 2. Confirm database initialization parameters
# Note: If this is the first time settings.application.isinit is set, please set the current value to true, the system will automatically initialize the database structure and basic data information;
# 3. Confirm the log path
# Start the project or debug with the IDE
./go-admin
# See also instructions in WIKI
```
Document generation
```bash
swag init
```
If there is no `swag` command go get installed
```bash
go get -u github.com/swaggo/swag/cmd/swag
```
Cross compilation
```bash
env GOOS=windows GOARCH=amd64 go build main.go
# or
env GOOS=linux GOARCH=amd64 go build main.go
```
## 🔗 Online Demo
> admin / 123456
Demo address:[http://www.zhangwj.com](http://www.zhangwj.com/#/login)
## 🤝 Open source projects used
[gin](https://github.com/gin-gonic/gin)
[casbin](https://github.com/casbin/casbin)
[spf13/viper](https://github.com/spf13/viper)
[gorm](https://github.com/jinzhu/gorm)
[gin-swagger](https://github.com/swaggo/gin-swagger)
[jwt-go](https://github.com/dgrijalva/jwt-go)
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
[ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
## Version
#### 2020-03-15 New Features and Optimization
1. Add user avatar upload
2. Add user password modification
3. Operation log page adjustment
4. Optimize captcha background color
I saw a lot of friends who experience the wrong verification code, so I adjusted the contrast for everyone to experience!
## 🤝 Thanks
[chengxiao](https://github.com/chengxiao)
## License
[MIT](https://github.com/wenjianzhang/go-admin/blob/master/LICENSE.md)
Copyright (c) 2020 wenjianzhang
[中文]qq technical exchange group: 74520518
+350
View File
@@ -0,0 +1,350 @@
# go-admin
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
[![Build Status](https://github.com/go-admin-team/go-admin/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/go-admin-team/go-admin)
[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases)
[![License](https://img.shields.io/github/license/go-admin-team/go-admin.svg)](https://github.com/go-admin-team/go-admin)
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md) | [繁體中文](https://github.com/go-admin-team/go-admin/blob/master/README.zh-TW.md) | 日本語
Gin + Vue + Element UI / Arco Design / Ant Design による、フロントエンドとバックエンドを分離した権限管理システムです。初期化は非常に簡単で、設定ファイルのデータベース接続情報を変更するだけで動作します。複数のコマンドに対応しており、マイグレーションコマンドでデータベースの初期化が容易になり、サーバーコマンドで API を手軽に起動できます。
[オンラインドキュメント](https://www.go-admin.pro)
[フロントエンドプロジェクト](https://github.com/go-admin-team/go-admin-ui)
[動画チュートリアル](https://space.bilibili.com/565616721/channel/detail?cid=125737)
## 🎬 オンラインデモ
Element Plus vue3 デモ:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
> ⚠️⚠️⚠️ アカウント / パスワード: admin / 123456
antd デモ(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> ⚠️⚠️⚠️ アカウント / パスワード: admin / 123456
## ✨ 特徴
- RESTful API の設計規約に準拠
- GIN WEB API フレームワークをベースに、豊富なミドルウェアを提供(ユーザー認証、CORS、アクセスログ、トレース ID など)
- Casbin による RBAC アクセス制御モデル
- JWT 認証
- Swagger ドキュメントに対応(swaggo ベース)
- GORM によるデータベース永続化、複数種類のデータベースに拡張可能
- 設定ファイルからモデルへの単純なマッピングで、必要な設定をすぐに取得
- コード生成ツール
- フォームビルダー
- マルチコマンド方式
- マルチテナント対応
- TODO: ユニットテスト
## 🎁 標準機能
1. マルチテナント:デフォルトで対応。データベース単位で分離し、1 データベースにつき 1 テナント。
1. ユーザー管理:システムの操作者であるユーザーの設定を行います。
2. 部門管理:組織構造(会社・部門・グループ)を設定します。ツリー構造で表示し、データ権限に対応します。
3. 役職管理:ユーザーが担当する職務を設定します。
4. メニュー管理:メニュー、操作権限、ボタン権限識別子、API 権限などを設定します。
5. ロール管理:ロールへのメニュー権限の割り当て、および組織単位でのデータ範囲権限の設定を行います。
6. 辞書管理:システム内で頻繁に使う固定的なデータを管理します。
7. パラメータ管理:よく使うパラメータを動的に設定します。
8. 操作ログ:正常系の操作ログと異常情報のログを記録・検索します。
9. ログインログ:ログイン履歴を記録・検索します。ログイン異常も含みます。
1. API ドキュメント:業務コードから API ドキュメントを自動生成します。
1. コード生成:テーブル定義から CRUD 業務を生成します。すべて画面上で操作でき、基本的な業務をコードなしで実現できます。
1. フォームビルダー:ページのスタイルをカスタマイズし、ドラッグ&ドロップでレイアウトを作成します。
1. サービス監視:サーバーの基本情報を確認します。
1. コンテンツ管理:デモ機能。カテゴリ管理とコンテンツ管理を含み、入門用の参考実装として利用できます。
1. スケジュールタスク:自動実行タスク。現在は API 呼び出しと関数呼び出しに対応しています。
## 事前準備
ローカルに [go] [gin] [node](http://nodejs.org/) と [git](https://git-scm.com/) をインストールしてください。
ダウンロードから使いこなすまでを解説した動画とドキュメントのチュートリアルを用意しています。本プロジェクトを試す前に、まずこれらに目を通すことを強くおすすめします。
### go-admin で最初のアプリケーションを作る - ドキュメント
[ステップ 1 - 基礎の紹介](https://www.go-admin.pro/guide/intro/tutorial01.html)
[ステップ 2 - 実践 - CRUD を書く](https://www.go-admin.pro/guide/intro/tutorial02.html)
### 動画チュートリアル
[go-admin の起動方法](https://www.bilibili.com/video/BV1z5411x7JG)
[生成ツールで業務を手軽に実装する](https://www.bilibili.com/video/BV1Dg4y1i79D)
[v1.1.0 のコード生成ツール](https://www.bilibili.com/video/BV1N54y1i71P) [応用]
[マルチコマンドでの起動方法と IDE 設定](https://www.bilibili.com/video/BV1Fg4y1q7ph)
[go-admin のメニュー設定](https://www.bilibili.com/video/BV1Wp4y1D715) [必見]
[メニュー情報と API 情報の設定方法](https://www.bilibili.com/video/BV1zv411B7nG) [必見]
[go-admin の権限設定](https://www.bilibili.com/video/BV1rt4y197d3) [必見]
[go-admin のデータ権限](https://www.bilibili.com/video/BV1LK4y1s71e) [必見]
**不明点はまず上記のドキュメントと記事をご確認ください。解決しない場合は issue や pr をお寄せください。動画とドキュメントは継続的に更新しています**
## 📦 ローカル開発
### 動作要件
go 1.26.5
node バージョン: v22 以上(v24 LTS 推奨)
パッケージマネージャー: pnpm v9 以上(UI プロジェクトは pnpm を使用)
### 開発ディレクトリの作成
```bash
# 開発ディレクトリを作成
mkdir goadmin
cd goadmin
```
### コードの取得
> 重要:2 つのプロジェクトは同じディレクトリに配置してください。
```bash
# バックエンドのコードを取得
git clone https://github.com/go-admin-team/go-admin.git
# フロントエンドのコードを取得
git clone https://github.com/go-admin-team/go-admin-ui.git
```
### 起動方法
#### サーバーの起動
```bash
# go-admin バックエンドプロジェクトへ移動
cd ./go-admin
# 依存関係を整理
go mod tidy
# ビルド
go build
# 設定を変更
# ファイルパス go-admin/config/settings.yml
vi ./config/settings.yml
# 1. 設定ファイル内のデータベース情報を変更
# 注意: settings.database 配下の設定項目
# 2. log のパスを確認
```
⚠️注意 Windows 環境で CGO が未導入の場合、次のエラーが発生します。
```bash
E:\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
```
or
```bash
D:\Code\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
```
[cgo の問題の解決方法はこちら](https://www.go-admin.pro/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
#### データベースの初期化とサービス起動
``` bash
# 初回はデータベースのリソース情報を初期化する必要があります
# macOS または linux の場合
$ ./go-admin migrate -c config/settings.dev.yml
# ⚠️注意: windows の場合
$ go-admin.exe migrate -c config/settings.dev.yml
# プロジェクトを起動します。IDE からデバッグ実行することもできます
# macOS または linux の場合
$ ./go-admin server -c config/settings.yml
# ⚠️注意: windows の場合
$ go-admin.exe server -c config/settings.yml
```
#### sys_api テーブルへのデータ追加方法
起動時に `-a true` を付けると、不足している API データが自動的に追加されます。
```bash
./go-admin server -c config/settings.yml -a true
```
#### docker でのビルドと起動
```shell
# イメージをビルド
docker build -t go-admin .
# コンテナを起動します。1 つ目の go-admin はコンテナ名、2 つ目はイメージ名です
# -v は設定ファイルのマウント ローカルパス:コンテナ内パス
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
```
#### ドキュメント生成
```bash
go generate
```
#### クロスコンパイル
```bash
# windows
env GOOS=windows GOARCH=amd64 go build main.go
# or
# linux
env GOOS=linux GOARCH=amd64 go build main.go
```
### UI 側の起動方法
```bash
# pnpm をインストール(未導入の場合)
npm install -g pnpm
# 依存関係をインストール
pnpm install
# 中国本土のネットワークではミラーを指定すると高速化できます
pnpm install --registry=https://registry.npmmirror.com
# 開発サーバーを起動
pnpm dev
```
## 📨 コミュニティ
<table>
<tr>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
</tr>
<tr>
<td>微信</td>
<td>公众号🔥🔥🔥</td>
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
<td>哔哩哔哩🔥🔥🔥</td>
</tr>
</table>
## 💎 コントリビューター
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
## JetBrains のオープンソースライセンス支援
`go-admin` は一貫して JetBrains 社の GoLand 統合開発環境で開発されています。**free JetBrains Open Source license(s)** による正規の無償ライセンス提供に、この場を借りて感謝を申し上げます。
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
## 🤝 謝辞
1. [ant-design](https://github.com/ant-design/ant-design)
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
2. [arco-design](https://github.com/arco-design/arco-design)
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
4. [gin](https://github.com/gin-gonic/gin)
5. [casbin](https://github.com/casbin/casbin)
6. [spf13/viper](https://github.com/spf13/viper)
7. [gorm](https://github.com/go-gorm/gorm)
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
9. [golang-jwt](https://github.com/golang-jwt/jwt)
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
12. [form-generator](https://github.com/JakHuang/form-generator)
## 🤟 支援
> このプロジェクトがお役に立ちましたら、作者にジュースを一杯おごる形で応援いただけます :tropical_drink:
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
## 🤝 関連リンク
- [mss-boot-io](https://docs.mss-boot-io.top/)
## 🔑 License
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
Copyright (c) 2026 wenjianzhang
+250 -159
View File
@@ -1,248 +1,339 @@
<p align="center">
<img width="320" src="https://gitee.com/mydearzwj/image/raw/master/img/go-admin.svg">
</p>
# go-admin
<img align="right" width="320" src="https://raw.githubusercontent.com/wenjianzhang/image/203c5930b9ed08d5cf2fcb4516b85e412f8e0e60/img/go-admin.svg">
<p align="center">
<a href="https://github.com/wenjianzhang/go-admin">
<img src="https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg" alt="go-admin">
</a>
<a href="https://github.com/wenjianzhang/go-admin">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
<a href="http://doc.zhangwj.com/go-admin-site/donate/">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
</a>
</p>
[![Build Status](https://github.com/go-admin-team/go-admin/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/go-admin-team/go-admin)
[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases)
[![License](https://img.shields.io/github/license/go-admin-team/go-admin.svg)](https://github.com/go-admin-team/go-admin)
English | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md) | [繁體中文](https://github.com/go-admin-team/go-admin/blob/master/README.zh-TW.md) | [日本語](https://github.com/go-admin-team/go-admin/blob/master/README.ja-JP.md)
The front-end and back-end separation authority management system based on Gin + Vue + Element UI OR Arco Design OR Ant Design is extremely simple to initialize the system. You only need to modify the database connection in the configuration file. The system supports multi-instruction operations. Migration instructions can make it easier to initialize database information. Service instructions It's easy to start the api service.
[documentation](https://www.go-admin.pro)
[Front-end project](https://github.com/go-admin-team/go-admin-ui)
[Video tutorial](https://space.bilibili.com/565616721/channel/detail?cid=125737)
## 🎬 Online Demo
Element Plus vue3 demo:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
> Account / Password: admin / 123456
antd demo (go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> Account / Password: admin / 123456
>
## ✨ Feature
- Follow RESTful API design specifications
- Based on the GIN WEB API framework, it provides rich middleware support (user authentication, cross-domain, access log, tracking ID, etc.)
- RBAC access control model based on Casbin
- JWT authentication
- Support Swagger documents (based on swaggo)
- Database storage based on GORM, which can expand multiple types of databases
- Simple model mapping of configuration files to quickly get the desired configuration
- Code generation tool
- Form builder
- Multi-command mode
- TODO: unit test
[English](https://github.com/wenjianzhang/go-admin/blob/master/README.en.md) | 简体中文
## 🎁 Internal
##### 基于Gin + Vue + Element UI的前后端分离权限管理系统
1. User management: The user is the system operator, this function mainly completes the system user configuration.
2. Department management: configure the system organization (company, department, group), and display the tree structure to support data permissions.
3. Position management: configure the positions of system users.
4. Menu management: configure the system menu, operation authority, button authority identification, interface authority, etc.
5. Role management: Role menu permission assignment and role setting are divided into data scope permissions by organization.
6. Dictionary management: Maintain some relatively fixed data frequently used in the system.
7. Parameter management: dynamically configure common parameters for the system.
8. Operation log: system normal operation log record and query; system abnormal information log record and query.
9. Login log: The system login log record query contains login exceptions.
1. Interface documentation: Automatically generate related api interface documents according to the business code.
1. Code generation: According to the data table structure, generate the corresponding addition, deletion, modification, and check corresponding business, and the whole process of visual operation, so that the basic business can be implemented with zero code.
1. Form construction: Customize the page style, drag and drop to realize the page layout.
1. Service monitoring: View the basic information of some servers.
1. Content management: demo function, including classification management and content management. You can refer to the easy to use quick start.
系统初始化极度简单,只需要配置文件中,修改数据库连接,系统启动后会自动初始化数据库信息以及必须的基础数据
## Ready to work
[在线文档国际](https://wenjianzhang.github.io/go-admin-site)
[在线文档国内](http://mydearzwj.gitee.io/go-admin-site/)
You need to install locally [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
[前端项目](https://github.com/wenjianzhang/go-admin-ui)
At the same time, a series of tutorials including videos and documents are provided. How to complete the downloading to the proficient use, it is strongly recommended that you read these tutorials before you practice this project! ! !
[视频教程](https://space.bilibili.com/565616721/channel/detail?cid=125737)
### Easily implement go-admin to write the first application-documentation tutorial
## ✨ 特性
[Step 1 - basic content introduction](https://www.go-admin.pro/guide/intro/tutorial01.html)
- 遵循 RESTful API 设计规范
[Step 2 - Practical application - writing database operations](https://www.go-admin.pro/guide/intro/tutorial02.html)
- 基于 GIN WEB API 框架,提供了丰富的中间件支持(用户认证、跨域、访问日志、追踪ID等)
### Teach you from getting started to giving up-video tutorial
- 基于Casbin的 RBAC 访问控制模型
[How to start go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
- JWT 认证
[Easily implement business using build tools](https://www.bilibili.com/video/BV1Dg4y1i79D)
- 支持 Swagger 文档(基于swaggo)
[v1.1.0 version code generation tool-free your hands](https://www.bilibili.com/video/BV1N54y1i71P) [Advanced]
- 基于 GORM 的数据库存储,可扩展多种类型数据库
[Explanation of multi-command startup mode and IDE configuration](https://www.bilibili.com/video/BV1Fg4y1q7ph)
- 配置文件简单的模型映射,快速能够得到想要的配置
[Configuration instructions for go-admin menu](https://www.bilibili.com/video/BV1Wp4y1D715) [Must see]
- 代码生成工具
[How to configure menu information and interface information](https://www.bilibili.com/video/BV1zv411B7nG) [Must see]
- 表单构建工具
[go-admin permission configuration instructions](https://www.bilibili.com/video/BV1rt4y197d3) [Must see]
- 多命令模式
[Instructions for use of go-admin data permissions](https://www.bilibili.com/video/BV1LK4y1s71e) [Must see]
- TODO: 单元测试
**If you have any questions, please read the above-mentioned usage documents and articles first. If you are not satisfied, welcome to issue and pr. Video tutorials and documents are being updated continuously.**
## 📦 Local development
## 🎁 内置
### Environmental requirements
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
9. 登录日志:系统登录日志记录查询包含登录异常。
10. 系统接口:根据业务代码自动生成相关的api接口文档。
11. 代码生成:根据数据表结构生成对应的增删改查相对应业务,全部可视化编程,基本业务可以0代码实现。
12. 表单构建:自定义页面样式,拖拉拽实现页面布局。
13. 服务监控:查看一些服务器的基本信息。
go 1.26.5
## 准备工作
nodejs: v22+ (v24 LTS recommended)
你需要在本地安装 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
package manager: pnpm v9+ (the UI project uses pnpm)
同时配套了系列教程包含视频和文档,如何从下载完成到熟练使用,强烈建议大家先看完这些教程再来实践本项目!!!
### 轻松实现go-admin写出第一个应用 - 文档教程
[步骤一 - 基础内容介绍](http://doc.zhangwj.com/go-admin-site/guide/intro/tutorial01.html)
[步骤二 - 实际应用 - 编写增删改查](http://doc.zhangwj.com/go-admin-site/guide/intro/tutorial02.html)
### 手把手教你从入门到放弃 - 视频教程
[如何启动go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
[使用生成工具轻松实现业务](https://www.bilibili.com/video/BV1Dg4y1i79D)
[多命令启动方式讲解以及IDE配置](https://www.bilibili.com/video/BV1Fg4y1q7ph)
[go-admin菜单的配置说明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看]
[如何配置菜单信息以及接口信息](https://www.bilibili.com/video/BV1zv411B7nG) [必看]
[go-admin权限配置使用说明](https://www.bilibili.com/video/BV1rt4y197d3) [必看]
[go-admin数据权限使用说明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看]
**如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr ,视频教程和文档持续更新中**
## 🗞 系统架构
<p align="center">
<img src="https://gitee.com/mydearzwj/image/raw/d9f59ea603e3c8a3977491a1bfa8f122e1a80824/img/go-admin-system.png" width="936px" height="491px">
</p>
## 📦 本地开发
### 开发目录创建
### Development directory creation
```bash
# 创建开发目录
# Create a development directory
mkdir goadmin
cd goadmin
```
### 获取代码
### Get the code
> 重点注意:两个项目必须放在同一文件夹下;
> Important note: the two projects must be placed in the same folder;
```bash
# 获取后端代码
git clone https://github.com/wenjianzhang/go-admin.git
# Get backend code
git clone https://github.com/go-admin-team/go-admin.git
# 获取前端代码
git clone https://github.com/wenjianzhang/go-admin-ui.git
# Get the front-end code
git clone https://github.com/go-admin-team/go-admin-ui.git
```
### Startup instructions
### 启动说明
#### 服务端启动说明
#### Server startup instructions
```bash
# 进入 go-admin 后端项目
# Enter the go-admin backend project
cd ./go-admin
# 编译项目
# Update dependencies
go mod tidy
# Compile the project
go build
# 修改配置
# 文件路径 go-admin/config/settings.yml
vi ./config/setting.yml
# Change setting
# File path go-admin/config/settings.yml
vi ./config/settings.yml
# 1. 配置文件中修改数据库信息
# 注意: settings.database 下对应的配置数据
# 2. 确认log路径
# 1. Modify the database information in the configuration file
# Note: The corresponding configuration data under settings.database
# 2. Confirm the log path
```
#### 初始化数据库,以及服务启动
```
# 首次配置需要初始化数据库资源信息
./go-admin init -c config/settings.yml -m dev
⚠️ Note that this problem will occur if CGO is not installed in the windows10+ environment;
# 启动项目,也可以用IDE进行调试
./go-admin server -c config/settings.yml -p 8000 -m dev
```
#### 文档生成
```bash
swag init
# 如果没有swag命令 go get安装一下即可
go get -u github.com/swaggo/swag/cmd/swag
E:\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
```
#### 交叉编译
or
```bash
D:\Code\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
```
[Solve the cgo problem and enter](https://www.go-admin.pro/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
#### Initialize the database, and start the service
``` bash
# The first configuration needs to initialize the database resource information
# Use under macOS or linux
$ ./go-admin migrate -c config/settings.dev.yml
# ⚠️Note: Use under windows
$ go-admin.exe migrate -c config/settings.dev.yml
# Start the project, you can also use the IDE for debugging
# Use under macOS or linux
$ ./go-admin server -c config/settings.yml
# ⚠️Note: Use under windows
$ go-admin.exe server -c config/settings.yml
```
#### Use docker to compile and start
```shell
# Compile the image
docker build -t go-admin .
# Start the container, the first go-admin is the container name, and the second go-admin is the image name
# -v Mapping configuration file Local path: container path
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
```
#### Generation Document
```bash
go generate
```
#### Cross compile
```bash
# windows
env GOOS=windows GOARCH=amd64 go build main.go
# or
# linux
env GOOS=linux GOARCH=amd64 go build main.go
```
### UI交互端启动说明
### UI interactive terminal startup instructions
```bash
# 安装依赖
npm install
# Install pnpm if you don't have it
npm install -g pnpm
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# Installation dependencies
pnpm install
# 启动服务
npm run dev
# Start service
pnpm dev
```
## 🎬 在线体验
> admin / 123456
演示地址:[http://www.zhangwj.com](http://www.zhangwj.com/#/login)
## 📨 互动
## 📨 Interactive
<table>
<tr>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq.png" width="200px"></td>
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
</tr>
<tr>
<td>微信</td>
<td>此群已满</td>
<td>Wechat</td>
<td>Wechat公众号🔥🔥🔥</td>
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
<td>bilibili🔥🔥🔥</td>
</tr>
</table>
## 🤝 特别感谢
[chengxiao](https://github.com/chengxiao)
[gin](https://github.com/gin-gonic/gin)
[casbin](https://github.com/casbin/casbin)
[spf13/viper](https://github.com/spf13/viper)
[gorm](https://github.com/jinzhu/gorm)
[gin-swagger](https://github.com/swaggo/gin-swagger)
[jwt-go](https://github.com/dgrijalva/jwt-go)
[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
[ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
## 💎 Contributors
## 🤟 打赏
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
> 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
## JetBrains open source certificate support
The `go-admin` project has always been developed in the GoLand integrated development environment under JetBrains, based on the **free JetBrains Open Source license(s)** genuine free license. I would like to express my gratitude.
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
## 🤝 Thanks
1. [ant-design](https://github.com/ant-design/ant-design)
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
2. [arco-design](https://github.com/arco-design/arco-design)
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
2. [gin](https://github.com/gin-gonic/gin)
2. [casbin](https://github.com/casbin/casbin)
2. [spf13/viper](https://github.com/spf13/viper)
2. [gorm](https://github.com/go-gorm/gorm)
2. [gin-swagger](https://github.com/swaggo/gin-swagger)
2. [golang-jwt](https://github.com/golang-jwt/jwt)
2. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
2. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
2. [form-generator](https://github.com/JakHuang/form-generator)
## 🤟 Sponsor Us
> If you think this project helped you, you can buy a glass of juice for the author to show encouragement :tropical_drink:
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
## ❤️ 赞助者
> 有部分是微信名称
zhuqiyun LLL狐 星星之火 cjj770 Sam 唐*i 晓聪 aLong *渊 海马 魏镇坪 + 111 *哥 我的宇哥哥 *声 *节
## 🤝 Link
- [mss-boot-io](https://docs.mss-boot-io.top/)
## 🔑 License
[MIT](https://github.com/wenjianzhang/go-admin/blob/master/LICENSE.md)
Copyright (c) 2020 wenjianzhang
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
Copyright (c) 2026 wenjianzhang
+350
View File
@@ -0,0 +1,350 @@
# go-admin
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
[![Build Status](https://github.com/go-admin-team/go-admin/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/go-admin-team/go-admin)
[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases)
[![License](https://img.shields.io/github/license/go-admin-team/go-admin.svg)](https://github.com/go-admin-team/go-admin)
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md) | 繁體中文 | [日本語](https://github.com/go-admin-team/go-admin/blob/master/README.ja-JP.md)
基於 Gin + Vue + Element UI OR Arco Design OR Ant Design 的前後端分離權限管理系統。系統初始化極為簡單,只需在設定檔中修改資料庫連線資訊即可。系統支援多指令操作:遷移指令讓資料庫初始化變得更簡單,服務指令則能輕鬆啟動 API 服務。
[線上文件](https://www.go-admin.pro)
[前端專案](https://github.com/go-admin-team/go-admin-ui)
[影片教學](https://space.bilibili.com/565616721/channel/detail?cid=125737)
## 🎬 線上體驗
Element Plus vue3 體驗:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
> ⚠️⚠️⚠️ 帳號 / 密碼: admin / 123456
antd 體驗(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> ⚠️⚠️⚠️ 帳號 / 密碼: admin / 123456
## ✨ 特性
- 遵循 RESTful API 設計規範
- 基於 GIN WEB API 框架,提供豐富的中介軟體支援(使用者認證、跨域、存取日誌、追蹤 ID 等)
- 基於 Casbin 的 RBAC 存取控制模型
- JWT 認證
- 支援 Swagger 文件(基於 swaggo)
- 基於 GORM 的資料庫儲存,可擴充多種類型資料庫
- 設定檔簡單的模型映射,快速取得所需設定
- 程式碼產生工具
- 表單建構工具
- 多指令模式
- 多租戶的支援
- TODO: 單元測試
## 🎁 內建
1. 多租戶:系統預設支援多租戶,按資料庫分離,一個資料庫一個租戶。
1. 使用者管理:使用者是系統操作者,該功能主要完成系統使用者設定。
2. 部門管理:設定系統組織架構(公司、部門、小組),以樹狀結構呈現並支援資料權限。
3. 職位管理:設定系統使用者所擔任的職務。
4. 選單管理:設定系統選單、操作權限、按鈕權限標識、介面權限等。
5. 角色管理:角色選單權限分配、設定角色按機構進行資料範圍權限劃分。
6. 字典管理:對系統中經常使用且較為固定的資料進行維護。
7. 參數管理:對系統動態設定常用參數。
8. 操作日誌:系統正常操作的日誌記錄與查詢;系統異常資訊的日誌記錄與查詢。
9. 登入日誌:系統登入日誌記錄查詢,包含登入異常。
1. 介面文件:根據業務程式碼自動產生相關的 API 介面文件。
1. 程式碼產生:根據資料表結構產生對應的增刪改查業務,全程視覺化操作,讓基本業務可以零程式碼實現。
1. 表單建構:自訂頁面樣式,拖拉放實現頁面佈局。
1. 服務監控:檢視伺服器的基本資訊。
1. 內容管理:demo 功能,下設分類管理、內容管理,可參考使用以快速入門。
1. 排程任務:自動化任務,目前支援介面呼叫與函式呼叫。
## 準備工作
你需要在本機安裝 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
同時配套了系列教學(含影片與文件),說明如何從下載到熟練使用。強烈建議先看完這些教學再來實作本專案!!!
### 輕鬆用 go-admin 寫出第一個應用 - 文件教學
[步驟一 - 基礎內容介紹](https://www.go-admin.pro/guide/intro/tutorial01.html)
[步驟二 - 實際應用 - 撰寫增刪改查](https://www.go-admin.pro/guide/intro/tutorial02.html)
### 手把手教你從入門到放棄 - 影片教學
[如何啟動 go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
[使用產生工具輕鬆實現業務](https://www.bilibili.com/video/BV1Dg4y1i79D)
[v1.1.0 版本程式碼產生工具 - 釋放雙手](https://www.bilibili.com/video/BV1N54y1i71P) [進階]
[多指令啟動方式講解以及 IDE 設定](https://www.bilibili.com/video/BV1Fg4y1q7ph)
[go-admin 選單的設定說明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看]
[如何設定選單資訊以及介面資訊](https://www.bilibili.com/video/BV1zv411B7nG) [必看]
[go-admin 權限設定使用說明](https://www.bilibili.com/video/BV1rt4y197d3) [必看]
[go-admin 資料權限使用說明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看]
**如有問題請先參閱上述文件與文章,若仍無法解決,歡迎提出 issue 與 pr。影片教學與文件持續更新中**
## 📦 本機開發
### 環境需求
go 1.26.5
node 版本: v22+(建議 v24 LTS)
套件管理器: pnpm v9+(UI 專案使用 pnpm)
### 建立開發目錄
```bash
# 建立開發目錄
mkdir goadmin
cd goadmin
```
### 取得程式碼
> 重點注意:兩個專案必須放在同一資料夾下;
```bash
# 取得後端程式碼
git clone https://github.com/go-admin-team/go-admin.git
# 取得前端程式碼
git clone https://github.com/go-admin-team/go-admin-ui.git
```
### 啟動說明
#### 伺服器端啟動說明
```bash
# 進入 go-admin 後端專案
cd ./go-admin
# 更新整理相依套件
go mod tidy
# 編譯專案
go build
# 修改設定
# 檔案路徑 go-admin/config/settings.yml
vi ./config/settings.yml
# 1. 在設定檔中修改資料庫資訊
# 注意: settings.database 下對應的設定資料
# 2. 確認 log 路徑
```
⚠️注意 在 Windows 環境若未安裝 CGO,會出現這個問題;
```bash
E:\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
```
or
```bash
D:\Code\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
```
[解決 cgo 問題請進入](https://www.go-admin.pro/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
#### 初始化資料庫,以及服務啟動
``` bash
# 首次設定需要初始化資料庫資源資訊
# macOS or linux 下使用
$ ./go-admin migrate -c config/settings.dev.yml
# ⚠️注意:windows 下使用
$ go-admin.exe migrate -c config/settings.dev.yml
# 啟動專案,也可以用 IDE 進行除錯
# macOS or linux 下使用
$ ./go-admin server -c config/settings.yml
# ⚠️注意:windows 下使用
$ go-admin.exe server -c config/settings.yml
```
#### sys_api 表的資料如何新增
在專案啟動時,使用 `-a true` 系統會自動新增缺少的介面資料
```bash
./go-admin server -c config/settings.yml -a true
```
#### 使用 docker 編譯啟動
```shell
# 編譯映像檔
docker build -t go-admin .
# 啟動容器,第一個 go-admin 是容器名稱,第二個 go-admin 是映像檔名稱
# -v 映射設定檔 本機路徑:容器路徑
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
```
#### 文件產生
```bash
go generate
```
#### 交叉編譯
```bash
# windows
env GOOS=windows GOARCH=amd64 go build main.go
# or
# linux
env GOOS=linux GOARCH=amd64 go build main.go
```
### UI 互動端啟動說明
```bash
# 安裝 pnpm(若未安裝)
npm install -g pnpm
# 安裝相依套件
pnpm install
# 中國大陸網路可指定鏡像來源加速
pnpm install --registry=https://registry.npmmirror.com
# 啟動服務
pnpm dev
```
## 📨 互動
<table>
<tr>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
</tr>
<tr>
<td>微信</td>
<td>公众号🔥🔥🔥</td>
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
<td>哔哩哔哩🔥🔥🔥</td>
</tr>
</table>
## 💎 貢獻者
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
## JetBrains 開源證書支援
`go-admin` 專案一直以來都是在 JetBrains 公司旗下的 GoLand 整合開發環境中進行開發,基於 **free JetBrains Open Source license(s)** 正版免費授權,在此表達我的謝意。
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
## 🤝 特別感謝
1. [ant-design](https://github.com/ant-design/ant-design)
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
2. [arco-design](https://github.com/arco-design/arco-design)
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
4. [gin](https://github.com/gin-gonic/gin)
5. [casbin](https://github.com/casbin/casbin)
6. [spf13/viper](https://github.com/spf13/viper)
7. [gorm](https://github.com/go-gorm/gorm)
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
9. [golang-jwt](https://github.com/golang-jwt/jwt)
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
12. [form-generator](https://github.com/JakHuang/form-generator)
## 🤟 贊助
> 如果你覺得這個專案幫助到了你,可以幫作者買一杯果汁表示鼓勵 :tropical_drink:
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
## 🤝 連結
- [mss-boot-io](https://docs.mss-boot-io.top/)
## 🔑 License
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
Copyright (c) 2026 wenjianzhang
-132
View File
@@ -1,132 +0,0 @@
package log
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
"net/http"
)
// @Summary 登录日志列表
// @Description 获取JSON
// @Tags 登录日志
// @Param status query string false "status"
// @Param dictCode query string false "dictCode"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/loginloglist [get]
// @Security Bearer
func GetLoginLogList(c *gin.Context) {
var data models.LoginLog
var err error
var pageSize = 10
var pageIndex = 1
size := c.Request.FormValue("pageSize")
if size != "" {
pageSize = tools.StrToInt(err, size)
}
index := c.Request.FormValue("pageIndex")
if index != "" {
pageIndex = tools.StrToInt(err, index)
}
data.Username = c.Request.FormValue("username")
data.Status = c.Request.FormValue("status")
data.Ipaddr = c.Request.FormValue("ipaddr")
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 通过编码获取登录日志
// @Description 获取JSON
// @Tags 登录日志
// @Param infoId path int true "infoId"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/loginlog/{infoId} [get]
// @Security Bearer
func GetLoginLog(c *gin.Context) {
var LoginLog models.LoginLog
LoginLog.InfoId, _ = tools.StringToInt(c.Param("infoId"))
result, err := LoginLog.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 添加登录日志
// @Description 获取JSON
// @Tags 登录日志
// @Accept application/json
// @Product application/json
// @Param data body models.LoginLog true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/loginlog [post]
// @Security Bearer
func InsertLoginLog(c *gin.Context) {
var data models.LoginLog
err := c.BindWith(&data, binding.JSON)
tools.HasError(err, "", 500)
result, err := data.Create()
tools.HasError(err, "", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 修改登录日志
// @Description 获取JSON
// @Tags 登录日志
// @Accept application/json
// @Product application/json
// @Param data body models.LoginLog true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/loginlog [put]
// @Security Bearer
func UpdateLoginLog(c *gin.Context) {
var data models.LoginLog
err := c.BindWith(&data, binding.JSON)
tools.HasError(err, "", -1)
result, err := data.Update(data.InfoId)
tools.HasError(err, "", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 批量删除登录日志
// @Description 删除数据
// @Tags 登录日志
// @Param infoId path string true "以逗号(,)分割的infoId"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/loginlog/{infoId} [delete]
func DeleteLoginLog(c *gin.Context) {
var data models.LoginLog
data.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("infoId", c)
_, err := data.BatchDelete(IDS)
tools.HasError(err, "修改失败", 500)
var res app.Response
res.Msg = "删除成功"
c.JSON(http.StatusOK, res.ReturnOK())
}
-111
View File
@@ -1,111 +0,0 @@
package log
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
"net/http"
)
// @Summary 登录日志列表
// @Description 获取JSON
// @Tags 登录日志
// @Param status query string false "status"
// @Param dictCode query string false "dictCode"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/operloglist [get]
// @Security Bearer
func GetOperLogList(c *gin.Context) {
var data models.SysOperLog
var err error
var pageSize = 10
var pageIndex = 1
size := c.Request.FormValue("pageSize")
if size != "" {
pageSize = tools.StrToInt(err, size)
}
index := c.Request.FormValue("pageIndex")
if index != "" {
pageIndex = tools.StrToInt(err, index)
}
data.OperName = c.Request.FormValue("operName")
data.Status = c.Request.FormValue("status")
data.OperIp = c.Request.FormValue("operIp")
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 通过编码获取登录日志
// @Description 获取JSON
// @Tags 登录日志
// @Param infoId path int true "infoId"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/operlog/{infoId} [get]
// @Security Bearer
func GetOperLog(c *gin.Context) {
var OperLog models.SysOperLog
OperLog.OperId, _ = tools.StringToInt(c.Param("operId"))
result, err := OperLog.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 添加操作日志
// @Description 获取JSON
// @Tags 操作日志
// @Accept application/json
// @Product application/json
// @Param data body models.SysOperLog true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/operlog [post]
// @Security Bearer
func InsertOperLog(c *gin.Context) {
var data models.SysOperLog
err := c.BindWith(&data, binding.JSON)
tools.HasError(err, "", 500)
result, err := data.Create()
tools.HasError(err, "", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 批量删除操作日志
// @Description 删除数据
// @Tags 操作日志
// @Param operId path string true "以逗号(,)分割的operId"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/operlog/{operId} [delete]
func DeleteOperLog(c *gin.Context) {
var data models.SysOperLog
data.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("operId", c)
_, err := data.BatchDelete(IDS)
tools.HasError(err, "删除失败", 500)
var res app.Response
res.Msg = "删除成功"
c.JSON(http.StatusOK, res.ReturnOK())
}
-57
View File
@@ -1,57 +0,0 @@
package monitor
import (
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"go-admin/tools/app"
"runtime"
)
const (
B = 1
KB = 1024 * B
MB = 1024 * KB
GB = 1024 * MB
)
func ServerInfo(c *gin.Context) {
osDic := make(map[string]interface{}, 0)
osDic["goOs"] = runtime.GOOS
osDic["arch"] = runtime.GOARCH
osDic["mem"] = runtime.MemProfileRate
osDic["compiler"] = runtime.Compiler
osDic["version"] = runtime.Version()
osDic["numGoroutine"] = runtime.NumGoroutine()
dis, _ := disk.Usage("/")
diskTotalGB := int(dis.Total) / GB
diskFreeGB := int(dis.Free) / GB
diskDic := make(map[string]interface{}, 0)
diskDic["total"] = diskTotalGB
diskDic["free"] = diskFreeGB
mem, _ := mem.VirtualMemory()
memUsedMB := int(mem.Used) / GB
memTotalMB := int(mem.Total) / GB
memFreeMB := int(mem.Free) / GB
memUsedPercent := int(mem.UsedPercent)
memDic := make(map[string]interface{}, 0)
memDic["total"] = memTotalMB
memDic["used"] = memUsedMB
memDic["free"] = memFreeMB
memDic["usage"] = memUsedPercent
cpuDic := make(map[string]interface{}, 0)
cpuDic["cpuNum"], _ = cpu.Counts(false)
app.Custum(c, gin.H{
"code": 200,
"os": osDic,
"mem": memDic,
"cpu": cpuDic,
"disk": diskDic,
})
}
-19
View File
@@ -1,19 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"go-admin/tools"
"go-admin/tools/app"
"go-admin/tools/captcha"
)
func GenerateCaptchaHandler(c *gin.Context) {
id, b64s, err := captcha.DriverDigitFunc()
tools.HasError(err, "验证码获取失败", 500)
app.Custum(c, gin.H{
"code": 200,
"data": b64s,
"id": id,
"msg": "success",
})
}
-147
View File
@@ -1,147 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
"go-admin/tools/app/msg"
"net/http"
)
// @Summary 配置列表数据
// @Description 获取JSON
// @Tags 配置
// @Param configKey query string false "configKey"
// @Param configName query string false "configName"
// @Param configType query string false "configType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/configList [get]
// @Security Bearer
func GetConfigList(c *gin.Context) {
var data models.SysConfig
var err error
var pageSize = 10
var pageIndex = 1
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize = tools.StrToInt(err, size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex = tools.StrToInt(err, index)
}
data.ConfigKey = c.Request.FormValue("configKey")
data.ConfigName = c.Request.FormValue("configName")
data.ConfigType = c.Request.FormValue("configType")
data.DataScope = tools.GetUserIdStr(c)
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 获取配置
// @Description 获取JSON
// @Tags 配置
// @Param configId path int true "配置编码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/config/{configId} [get]
// @Security Bearer
func GetConfig(c *gin.Context) {
var Config models.SysConfig
Config.ConfigId, _ = tools.StringToInt(c.Param("configId"))
result, err := Config.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 获取配置
// @Description 获取JSON
// @Tags 配置
// @Param configKey path int true "configKey"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/configKey/{configKey} [get]
// @Security Bearer
func GetConfigByConfigKey(c *gin.Context) {
var Config models.SysConfig
Config.ConfigKey = c.Param("configKey")
result, err := Config.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, result.ConfigValue)
}
// @Summary 添加配置
// @Description 获取JSON
// @Tags 配置
// @Accept application/json
// @Product application/json
// @Param data body models.SysConfig true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dict/data [post]
// @Security Bearer
func InsertConfig(c *gin.Context) {
var data models.SysConfig
err := c.BindWith(&data, binding.JSON)
data.CreateBy = tools.GetUserIdStr(c)
tools.HasError(err, "", 500)
result, err := data.Create()
tools.HasError(err, "", -1)
app.OK(c, result, "")
}
// @Summary 修改配置
// @Description 获取JSON
// @Tags 配置
// @Accept application/json
// @Product application/json
// @Param data body models.SysConfig true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/config [put]
// @Security Bearer
func UpdateConfig(c *gin.Context) {
var data models.SysConfig
err := c.BindWith(&data, binding.JSON)
tools.HasError(err, "数据解析失败", -1)
data.UpdateBy = tools.GetUserIdStr(c)
result, err := data.Update(data.ConfigId)
tools.HasError(err, "", -1)
app.OK(c, result, "")
}
// @Summary 删除配置
// @Description 删除数据
// @Tags 配置
// @Param configId path int true "configId"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/config/{configId} [delete]
func DeleteConfig(c *gin.Context) {
var data models.SysConfig
data.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("configId", c)
result, err := data.BatchDelete(IDS)
tools.HasError(err, "修改失败", 500)
app.OK(c, result, msg.DeletedSuccess)
}
-132
View File
@@ -1,132 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
"go-admin/tools/app/msg"
)
// @Summary 分页部门列表数据
// @Description 分页列表
// @Tags 部门
// @Param name query string false "name"
// @Param id query string false "id"
// @Param position query string false "position"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/deptList [get]
// @Security Bearer
func GetDeptList(c *gin.Context) {
var Dept models.SysDept
Dept.DeptName = c.Request.FormValue("deptName")
Dept.Status = c.Request.FormValue("status")
Dept.DeptId, _ = tools.StringToInt(c.Request.FormValue("deptId"))
Dept.DataScope = tools.GetUserIdStr(c)
result, err := Dept.SetDept(true)
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, "")
}
func GetDeptTree(c *gin.Context) {
var Dept models.SysDept
Dept.DeptName = c.Request.FormValue("deptName")
Dept.Status = c.Request.FormValue("status")
Dept.DeptId, _ = tools.StringToInt(c.Request.FormValue("deptId"))
result, err := Dept.SetDept(false)
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, "")
}
// @Summary 部门列表数据
// @Description 获取JSON
// @Tags 部门
// @Param deptId path string false "deptId"
// @Param position query string false "position"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dept/{deptId} [get]
// @Security Bearer
func GetDept(c *gin.Context) {
var Dept models.SysDept
Dept.DeptId, _ = tools.StringToInt(c.Param("deptId"))
Dept.DataScope = tools.GetUserIdStr(c)
result, err := Dept.Get()
tools.HasError(err, msg.NotFound, 404)
app.OK(c, result, msg.GetSuccess)
}
// @Summary 添加部门
// @Description 获取JSON
// @Tags 部门
// @Accept application/json
// @Product application/json
// @Param data body models.SysDept true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dept [post]
// @Security Bearer
func InsertDept(c *gin.Context) {
var data models.SysDept
err := c.BindWith(&data, binding.JSON)
tools.HasError(err, "", 500)
data.CreateBy = tools.GetUserIdStr(c)
result, err := data.Create()
tools.HasError(err, "", -1)
app.OK(c, result, msg.CreatedSuccess)
}
// @Summary 修改部门
// @Description 获取JSON
// @Tags 部门
// @Accept application/json
// @Product application/json
// @Param id path int true "id"
// @Param data body models.SysDept true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dept [put]
// @Security Bearer
func UpdateDept(c *gin.Context) {
var data models.SysDept
err := c.BindJSON(&data)
tools.HasError(err, "", -1)
data.UpdateBy = tools.GetUserIdStr(c)
result, err := data.Update(data.DeptId)
tools.HasError(err, "", -1)
app.OK(c, result, msg.UpdatedSuccess)
}
// @Summary 删除部门
// @Description 删除数据
// @Tags 部门
// @Param id path int true "id"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/dept/{id} [delete]
func DeleteDept(c *gin.Context) {
var data models.SysDept
id, err := tools.StringToInt(c.Param("id"))
_, err = data.Delete(id)
tools.HasError(err, "删除失败", 500)
app.OK(c, "", msg.DeletedSuccess)
}
func GetDeptTreeRoleselect(c *gin.Context) {
var Dept models.SysDept
var SysRole models.SysRole
id, err := tools.StringToInt(c.Param("roleId"))
SysRole.RoleId = id
result, err := Dept.SetDeptLable()
tools.HasError(err, msg.NotFound, -1)
menuIds := make([]int, 0)
if id != 0 {
menuIds, err = SysRole.GetRoleDeptId()
tools.HasError(err, "抱歉未找到相关信息", -1)
}
app.Custum(c, gin.H{
"code": 200,
"depts": result,
"checkedKeys": menuIds,
})
}
-152
View File
@@ -1,152 +0,0 @@
package dict
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
"net/http"
)
// @Summary 字典数据列表
// @Description 获取JSON
// @Tags 字典数据
// @Param status query string false "status"
// @Param dictCode query string false "dictCode"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/data/list [get]
// @Security Bearer
func GetDictDataList(c *gin.Context) {
var data models.DictData
var err error
var pageSize = 10
var pageIndex = 1
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize = tools.StrToInt(err, size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex = tools.StrToInt(err, index)
}
data.DictLabel = c.Request.FormValue("dictLabel")
data.Status = c.Request.FormValue("status")
data.DictType = c.Request.FormValue("dictType")
id := c.Request.FormValue("dictCode")
data.DictCode, _ = tools.StringToInt(id)
data.DataScope = tools.GetUserIdStr(c)
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 通过编码获取字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Param dictCode path int true "字典编码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/data/{dictCode} [get]
// @Security Bearer
func GetDictData(c *gin.Context) {
var DictData models.DictData
DictData.DictLabel = c.Request.FormValue("dictLabel")
DictData.DictCode, _ = tools.StringToInt(c.Param("dictCode"))
result, err := DictData.GetByCode()
tools.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 通过字典类型获取字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Param dictType path int true "dictType"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/databyType/{dictType} [get]
// @Security Bearer
func GetDictDataByDictType(c *gin.Context) {
var DictData models.DictData
DictData.DictType = c.Param("dictType")
result, err := DictData.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 添加字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Accept application/json
// @Product application/json
// @Param data body models.DictType true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dict/data [post]
// @Security Bearer
func InsertDictData(c *gin.Context) {
var data models.DictData
err := c.BindWith(&data, binding.JSON)
data.CreateBy = tools.GetUserIdStr(c)
tools.HasError(err, "", 500)
result, err := data.Create()
tools.HasError(err, "", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 修改字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Accept application/json
// @Product application/json
// @Param data body models.DictType true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dict/data [put]
// @Security Bearer
func UpdateDictData(c *gin.Context) {
var data models.DictData
err := c.BindWith(&data, binding.JSON)
data.UpdateBy = tools.GetUserIdStr(c)
tools.HasError(err, "", -1)
result, err := data.Update(data.DictCode)
tools.HasError(err, "", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 删除字典数据
// @Description 删除数据
// @Tags 字典数据
// @Param dictCode path int true "dictCode"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/dict/data/{dictCode} [delete]
func DeleteDictData(c *gin.Context) {
var data models.DictData
data.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("dictCode", c)
result, err := data.BatchDelete(IDS)
tools.HasError(err, "修改失败", 500)
app.OK(c, result, "删除成功")
}
-143
View File
@@ -1,143 +0,0 @@
package dict
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
"net/http"
)
// @Summary 字典类型列表数据
// @Description 获取JSON
// @Tags 字典类型
// @Param dictName query string false "dictName"
// @Param dictId query string false "dictId"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type/list [get]
// @Security Bearer
func GetDictTypeList(c *gin.Context) {
var data models.DictType
var err error
var pageSize = 10
var pageIndex = 1
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize = tools.StrToInt(err, size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex = tools.StrToInt(err, index)
}
data.DictName = c.Request.FormValue("dictName")
id := c.Request.FormValue("dictId")
data.DictId, _ = tools.StringToInt(id)
data.DictType = c.Request.FormValue("dictType")
data.DataScope = tools.GetUserIdStr(c)
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 通过字典id获取字典类型
// @Description 获取JSON
// @Tags 字典类型
// @Param dictId path int true "字典类型编码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type/{dictId} [get]
// @Security Bearer
func GetDictType(c *gin.Context) {
var DictType models.DictType
DictType.DictName = c.Request.FormValue("dictName")
DictType.DictId, _ = tools.StringToInt(c.Param("dictId"))
result, err := DictType.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
func GetDictTypeOptionSelect(c *gin.Context) {
var DictType models.DictType
DictType.DictName = c.Request.FormValue("dictName")
DictType.DictId, _ = tools.StringToInt(c.Param("dictId"))
result, err := DictType.GetList()
tools.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 添加字典类型
// @Description 获取JSON
// @Tags 字典类型
// @Accept application/json
// @Product application/json
// @Param data body models.DictType true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dict/type [post]
// @Security Bearer
func InsertDictType(c *gin.Context) {
var data models.DictType
err := c.BindWith(&data, binding.JSON)
data.CreateBy = tools.GetUserIdStr(c)
tools.HasError(err, "", 500)
result, err := data.Create()
tools.HasError(err, "", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 修改字典类型
// @Description 获取JSON
// @Tags 字典类型
// @Accept application/json
// @Product application/json
// @Param data body models.DictType true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dict/type [put]
// @Security Bearer
func UpdateDictType(c *gin.Context) {
var data models.DictType
err := c.BindWith(&data, binding.JSON)
data.UpdateBy = tools.GetUserIdStr(c)
tools.HasError(err, "", -1)
result, err := data.Update(data.DictId)
tools.HasError(err, "", -1)
var res app.Response
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 删除字典类型
// @Description 删除数据
// @Tags 字典类型
// @Param dictId path int true "dictId"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/dict/type/{dictId} [delete]
func DeleteDictType(c *gin.Context) {
var data models.DictType
data.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("dictId", c)
result, err := data.BatchDelete(IDS)
tools.HasError(err, "修改失败", 500)
app.OK(c, result, "删除成功")
}
-52
View File
@@ -1,52 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
)
func GetInfo(c *gin.Context) {
var roles = make([]string, 1)
roles[0] = tools.GetRoleName(c)
var permissions = make([]string, 1)
permissions[0] = "*:*:*"
var buttons = make([]string, 1)
buttons[0] = "*:*:*"
RoleMenu := models.RoleMenu{}
RoleMenu.RoleId = tools.GetRoleId(c)
var mp = make(map[string]interface{})
mp["roles"] = roles
if tools.GetRoleName(c) == "admin" || tools.GetRoleName(c) == "系统管理员" {
mp["permissions"] = permissions
mp["buttons"] = buttons
} else {
list, _ := RoleMenu.GetPermis()
mp["permissions"] = list
mp["buttons"] = list
}
sysuser := models.SysUser{}
sysuser.UserId = tools.GetUserId(c)
user, err := sysuser.Get()
tools.HasError(err, "", 500)
mp["introduction"] = " am a super administrator"
mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
if user.Avatar != "" {
mp["avatar"] = user.Avatar
}
mp["userName"] = user.NickName
mp["userId"] = user.UserId
mp["deptId"] = user.DeptId
mp["name"] = user.NickName
app.OK(c, mp, "")
}
-176
View File
@@ -1,176 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
)
// @Summary Menu列表数据
// @Description 获取JSON
// @Tags 菜单
// @Param menuName query string false "menuName"
// @Success 200 {string} string "{"code": 200, "data": [...]}"
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
// @Router /api/v1/menulist [get]
// @Security Bearer
func GetMenuList(c *gin.Context) {
var Menu models.Menu
Menu.MenuName = c.Request.FormValue("menuName")
Menu.Visible = c.Request.FormValue("visible")
Menu.Title = c.Request.FormValue("title")
Menu.DataScope = tools.GetUserIdStr(c)
result, err := Menu.SetMenu()
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, "")
}
// @Summary Menu列表数据
// @Description 获取JSON
// @Tags 菜单
// @Param menuName query string false "menuName"
// @Success 200 {string} string "{"code": 200, "data": [...]}"
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
// @Router /api/v1/menu [get]
// @Security Bearer
func GetMenu(c *gin.Context) {
var data models.Menu
id, err := tools.StringToInt(c.Param("id"))
data.MenuId = id
result, err := data.GetByMenuId()
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, "")
}
func GetMenuTreeRoleselect(c *gin.Context) {
var Menu models.Menu
var SysRole models.SysRole
id, err := tools.StringToInt(c.Param("roleId"))
SysRole.RoleId = id
result, err := Menu.SetMenuLable()
tools.HasError(err, "抱歉未找到相关信息", -1)
menuIds := make([]int, 0)
if id != 0 {
menuIds, err = SysRole.GetRoleMeunId()
tools.HasError(err, "抱歉未找到相关信息", -1)
}
app.Custum(c, gin.H{
"code": 200,
"menus": result,
"checkedKeys": menuIds,
})
}
// @Summary 获取菜单树
// @Description 获取JSON
// @Tags 菜单
// @Accept application/x-www-form-urlencoded
// @Product application/x-www-form-urlencoded
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/menuTreeselect [get]
// @Security Bearer
func GetMenuTreeelect(c *gin.Context) {
var data models.Menu
result, err := data.SetMenuLable()
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, "")
}
// @Summary 创建菜单
// @Description 获取JSON
// @Tags 菜单
// @Accept application/x-www-form-urlencoded
// @Product application/x-www-form-urlencoded
// @Param menuName formData string true "menuName"
// @Param Path formData string false "Path"
// @Param Action formData string true "Action"
// @Param Permission formData string true "Permission"
// @Param ParentId formData string true "ParentId"
// @Param IsDel formData string true "IsDel"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/menu [post]
// @Security Bearer
func InsertMenu(c *gin.Context) {
var data models.Menu
err := c.BindWith(&data, binding.JSON)
tools.HasError(err, "抱歉未找到相关信息", -1)
data.CreateBy = tools.GetUserIdStr(c)
result, err := data.Create()
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, "")
}
// @Summary 修改菜单
// @Description 获取JSON
// @Tags 菜单
// @Accept application/x-www-form-urlencoded
// @Product application/x-www-form-urlencoded
// @Param id path int true "id"
// @Param data body models.Menu true "body"
// @Success 200 {string} string "{"code": 200, "message": "修改成功"}"
// @Success 200 {string} string "{"code": -1, "message": "修改失败"}"
// @Router /api/v1/menu/{id} [put]
// @Security Bearer
func UpdateMenu(c *gin.Context) {
var data models.Menu
err2 := c.BindWith(&data, binding.JSON)
data.UpdateBy = tools.GetUserIdStr(c)
tools.HasError(err2, "修改失败", -1)
_, err := data.Update(data.MenuId)
tools.HasError(err, "", 501)
app.OK(c, "", "修改成功")
}
// @Summary 删除菜单
// @Description 删除数据
// @Tags 菜单
// @Param id path int true "id"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/menu/{id} [delete]
func DeleteMenu(c *gin.Context) {
var data models.Menu
id, err := tools.StringToInt(c.Param("id"))
data.UpdateBy = tools.GetUserIdStr(c)
_, err = data.Delete(id)
tools.HasError(err, "删除失败", 500)
app.OK(c, "", "删除成功")
}
// @Summary 根据角色名称获取菜单列表数据(左菜单使用)
// @Description 获取JSON
// @Tags 菜单
// @Param id path int true "id"
// @Success 200 {string} string "{"code": 200, "data": [...]}"
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
// @Router /api/v1/menurole [get]
// @Security Bearer
func GetMenuRole(c *gin.Context) {
var Menu models.Menu
result, err := Menu.SetMenuRole(tools.GetRoleName(c))
tools.HasError(err, "获取失败", 500)
app.OK(c, result, "")
}
// @Summary 获取角色对应的菜单id数组
// @Description 获取JSON
// @Tags 菜单
// @Param id path int true "id"
// @Success 200 {string} string "{"code": 200, "data": [...]}"
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
// @Router /api/v1/menuids/{id} [get]
// @Security Bearer
func GetMenuIDS(c *gin.Context) {
var data models.RoleMenu
data.RoleName = c.GetString("role")
data.UpdateBy = tools.GetUserIdStr(c)
result, err := data.GetIDS()
tools.HasError(err, "获取失败", 500)
app.OK(c, result, "")
}
-117
View File
@@ -1,117 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
)
// @Summary 岗位列表数据
// @Description 获取JSON
// @Tags 岗位
// @Param postName query string false "postName"
// @Param postCode query string false "postCode"
// @Param postId query string false "postId"
// @Param status query string false "status"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post [get]
// @Security Bearer
func GetPostList(c *gin.Context) {
var data models.Post
var err error
var pageSize = 10
var pageIndex = 1
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize = tools.StrToInt(err, size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex = tools.StrToInt(err, index)
}
id := c.Request.FormValue("postId")
data.PostId, _ = tools.StringToInt(id)
data.PostCode = c.Request.FormValue("postCode")
data.PostName = c.Request.FormValue("postName")
data.Status = c.Request.FormValue("status")
data.DataScope = tools.GetUserIdStr(c)
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
app.PageOK(c, result, count, pageIndex, pageSize, "")
}
// @Summary 获取岗位信息
// @Description 获取JSON
// @Tags 岗位
// @Param postId path int true "postId"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post/{postId} [get]
// @Security Bearer
func GetPost(c *gin.Context) {
var Post models.Post
Post.PostId, _ = tools.StringToInt(c.Param("postId"))
result, err := Post.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
app.OK(c, result, "")
}
// @Summary 添加岗位
// @Description 获取JSON
// @Tags 岗位
// @Accept application/json
// @Product application/json
// @Param data body models.Post true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/post [post]
// @Security Bearer
func InsertPost(c *gin.Context) {
var data models.Post
err := c.Bind(&data)
data.CreateBy = tools.GetUserIdStr(c)
tools.HasError(err, "", 500)
result, err := data.Create()
tools.HasError(err, "", -1)
app.OK(c, result, "")
}
// @Summary 修改岗位
// @Description 获取JSON
// @Tags 岗位
// @Accept application/json
// @Product application/json
// @Param data body models.Post true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/post/ [put]
// @Security Bearer
func UpdatePost(c *gin.Context) {
var data models.Post
err := c.Bind(&data)
data.UpdateBy = tools.GetUserIdStr(c)
tools.HasError(err, "", -1)
result, err := data.Update(data.PostId)
tools.HasError(err, "", -1)
app.OK(c, result, "修改成功")
}
// @Summary 删除岗位
// @Description 删除数据
// @Tags 岗位
// @Param id path int true "id"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 500 {string} string "{"code": 500, "message": "删除失败"}"
// @Router /api/v1/post/{postId} [delete]
func DeletePost(c *gin.Context) {
var data models.Post
data.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("postId", c)
result, err := data.BatchDelete(IDS)
tools.HasError(err, "删除失败", 500)
app.OK(c, result, "删除成功")
}
-149
View File
@@ -1,149 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
)
// @Summary 角色列表数据
// @Description Get JSON
// @Tags 角色/Role
// @Param roleName query string false "roleName"
// @Param status query string false "status"
// @Param roleKey query string false "roleKey"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/rolelist [get]
// @Security Bearer
func GetRoleList(c *gin.Context) {
var data models.SysRole
var err error
var pageSize = 10
var pageIndex = 1
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize = tools.StrToInt(err, size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex = tools.StrToInt(err, index)
}
data.RoleKey = c.Request.FormValue("roleKey")
data.RoleName = c.Request.FormValue("roleName")
data.Status = c.Request.FormValue("status")
data.DataScope = tools.GetUserIdStr(c)
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
app.PageOK(c, result, count, pageIndex, pageSize, "")
}
// @Summary 获取Role数据
// @Description 获取JSON
// @Tags 角色/Role
// @Param roleId path string false "roleId"
// @Success 200 {string} string "{"code": 200, "data": [...]}"
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
// @Router /api/v1/role [get]
// @Security Bearer
func GetRole(c *gin.Context) {
var Role models.SysRole
Role.RoleId, _ = tools.StringToInt(c.Param("roleId"))
result, err := Role.Get()
menuIds := make([]int, 0)
menuIds, err = Role.GetRoleMeunId()
tools.HasError(err, "抱歉未找到相关信息", -1)
result.MenuIds = menuIds
app.OK(c, result, "")
}
// @Summary 创建角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body models.SysRole true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/role [post]
func InsertRole(c *gin.Context) {
var data models.SysRole
data.CreateBy = tools.GetUserIdStr(c)
err := c.BindWith(&data, binding.JSON)
tools.HasError(err, "", 500)
id, err := data.Insert()
data.RoleId = id
tools.HasError(err, "", -1)
var t models.RoleMenu
_, err = t.Insert(id, data.MenuIds)
tools.HasError(err, "", -1)
app.OK(c, data, "添加成功")
}
// @Summary 修改用户角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body models.SysRole true "body"
// @Success 200 {string} string "{"code": 200, "message": "修改成功"}"
// @Success 200 {string} string "{"code": -1, "message": "修改失败"}"
// @Router /api/v1/role [put]
func UpdateRole(c *gin.Context) {
var data models.SysRole
data.UpdateBy = tools.GetUserIdStr(c)
err := c.Bind(&data)
tools.HasError(err, "数据解析失败", -1)
result, err := data.Update(data.RoleId)
tools.HasError(err, "", -1)
var t models.RoleMenu
_, err = t.DeleteRoleMenu(data.RoleId)
tools.HasError(err, "添加失败1", -1)
_, err2 := t.Insert(data.RoleId, data.MenuIds)
tools.HasError(err2, "添加失败2", -1)
app.OK(c, result, "修改成功")
}
func UpdateRoleDataScope(c *gin.Context) {
var data models.SysRole
data.UpdateBy = tools.GetUserIdStr(c)
err := c.Bind(&data)
tools.HasError(err, "数据解析失败", -1)
result, err := data.Update(data.RoleId)
var t models.SysRoleDept
_, err = t.DeleteRoleDept(data.RoleId)
tools.HasError(err, "添加失败1", -1)
if data.DataScope == "2" {
_, err2 := t.Insert(data.RoleId, data.DeptIds)
tools.HasError(err2, "添加失败2", -1)
}
app.OK(c, result, "修改成功")
}
// @Summary 删除用户角色
// @Description 删除数据
// @Tags 角色/Role
// @Param roleId path int true "roleId"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/role/{roleId} [delete]
func DeleteRole(c *gin.Context) {
var Role models.SysRole
Role.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("roleId", c)
_, err := Role.BatchDelete(IDS)
tools.HasError(err, "删除失败1", -1)
var t models.RoleMenu
_, err = t.BatchDeleteRoleMenu(IDS)
tools.HasError(err, "删除失败1", -1)
app.OK(c, "", "删除成功")
}
-71
View File
@@ -1,71 +0,0 @@
package system
import (
"fmt"
"github.com/gin-gonic/gin"
"go-admin/models"
"go-admin/tools/app"
"net/http"
)
// @Summary RoleMenu列表数据
// @Description 获取JSON
// @Tags 角色菜单
// @Param RoleId query string false "RoleId"
// @Success 200 {string} string "{"code": 200, "data": [...]}"
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
// @Router /api/v1/rolemenu [get]
// @Security Bearer
func GetRoleMenu(c *gin.Context) {
var Rm models.RoleMenu
err := c.ShouldBind(&Rm)
result, err := Rm.Get()
var res app.Response
if err != nil {
res.Msg = "抱歉未找到相关信息"
c.JSON(http.StatusOK, res.ReturnError(200))
return
}
res.Data = result
c.JSON(http.StatusOK, res.ReturnOK())
}
type RoleMenuPost struct {
RoleId string
RoleMenu []models.RoleMenu
}
func InsertRoleMenu(c *gin.Context) {
var res app.Response
res.Msg = "添加成功"
c.JSON(http.StatusOK, res.ReturnOK())
return
}
// @Summary 删除用户菜单数据
// @Description 删除数据
// @Tags 角色菜单
// @Param id path string true "id"
// @Param menu_id query string false "menu_id"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/rolemenu/{id} [delete]
func DeleteRoleMenu(c *gin.Context) {
var t models.RoleMenu
id := c.Param("id")
menuId := c.Request.FormValue("menu_id")
fmt.Println(menuId)
_, err := t.Delete(id, menuId)
if err != nil {
var res app.Response
res.Msg = "删除失败"
c.JSON(http.StatusOK, res.ReturnError(200))
return
}
var res app.Response
res.Msg = "删除成功"
c.JSON(http.StatusOK, res.ReturnOK())
return
}
-233
View File
@@ -1,233 +0,0 @@
package system
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/google/uuid"
"go-admin/global"
"go-admin/models"
"go-admin/tools"
"go-admin/tools/app"
)
// @Summary 列表用户信息数据
// @Description 获取JSON
// @Tags 用户
// @Param username query string false "username"
// @Success 200 {string} string "{"code": 200, "data": [...]}"
// @Success 200 {string} string "{"code": -1, "message": "抱歉未找到相关信息"}"
// @Router /api/v1/sysUserList [get]
// @Security Bearer
func GetSysUserList(c *gin.Context) {
var data models.SysUser
var err error
var pageSize = 10
var pageIndex = 1
size := c.Request.FormValue("pageSize")
if size != "" {
pageSize = tools.StrToInt(err, size)
}
index := c.Request.FormValue("pageIndex")
if index != "" {
pageIndex = tools.StrToInt(err, index)
}
data.Username = c.Request.FormValue("username")
data.Status = c.Request.FormValue("status")
data.Phone = c.Request.FormValue("phone")
postId := c.Request.FormValue("postId")
data.PostId, _ = tools.StringToInt(postId)
deptId := c.Request.FormValue("deptId")
data.DeptId, _ = tools.StringToInt(deptId)
data.DataScope = tools.GetUserIdStr(c)
result, count, err := data.GetPage(pageSize, pageIndex)
tools.HasError(err, "", -1)
app.PageOK(c, result, count, pageIndex, pageSize, "")
}
// @Summary 获取用户
// @Description 获取JSON
// @Tags 用户
// @Param userId path int true "用户编码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sysUser/{userId} [get]
// @Security Bearer
func GetSysUser(c *gin.Context) {
var SysUser models.SysUser
SysUser.UserId, _ = tools.StringToInt(c.Param("userId"))
result, err := SysUser.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
var SysRole models.SysRole
var Post models.Post
roles, err := SysRole.GetList()
posts, err := Post.GetList()
postIds := make([]int, 0)
postIds = append(postIds, result.PostId)
roleIds := make([]int, 0)
roleIds = append(roleIds, result.RoleId)
app.Custum(c, gin.H{
"code": 200,
"data": result,
"postIds": postIds,
"roleIds": roleIds,
"roles": roles,
"posts": posts,
})
}
// @Summary 获取个人中心用户
// @Description 获取JSON
// @Tags 个人中心
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/profile [get]
// @Security Bearer
func GetSysUserProfile(c *gin.Context) {
var SysUser models.SysUser
userId := tools.GetUserIdStr(c)
SysUser.UserId, _ = tools.StringToInt(userId)
result, err := SysUser.Get()
tools.HasError(err, "抱歉未找到相关信息", -1)
var SysRole models.SysRole
var Post models.Post
var Dept models.SysDept
//获取角色列表
roles, err := SysRole.GetList()
//获取职位列表
posts, err := Post.GetList()
//获取部门列表
Dept.DeptId = result.DeptId
dept, err := Dept.Get()
postIds := make([]int, 0)
postIds = append(postIds, result.PostId)
roleIds := make([]int, 0)
roleIds = append(roleIds, result.RoleId)
app.Custum(c, gin.H{
"code": 200,
"data": result,
"postIds": postIds,
"roleIds": roleIds,
"roles": roles,
"posts": posts,
"dept": dept,
})
}
// @Summary 获取用户角色和职位
// @Description 获取JSON
// @Tags 用户
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sysUser [get]
// @Security Bearer
func GetSysUserInit(c *gin.Context) {
var SysRole models.SysRole
var Post models.Post
roles, err := SysRole.GetList()
posts, err := Post.GetList()
tools.HasError(err, "抱歉未找到相关信息", -1)
mp := make(map[string]interface{}, 2)
mp["roles"] = roles
mp["posts"] = posts
app.OK(c, mp, "")
}
// @Summary 创建用户
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body models.SysUser true "用户数据"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/sysUser [post]
func InsertSysUser(c *gin.Context) {
var sysuser models.SysUser
err := c.BindWith(&sysuser, binding.JSON)
tools.HasError(err, "非法数据格式", 500)
sysuser.CreateBy = tools.GetUserIdStr(c)
id, err := sysuser.Insert()
tools.HasError(err, "添加失败", 500)
app.OK(c, id, "添加成功")
}
// @Summary 修改用户数据
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body models.SysUser true "body"
// @Success 200 {string} string "{"code": 200, "message": "修改成功"}"
// @Success 200 {string} string "{"code": -1, "message": "修改失败"}"
// @Router /api/v1/sysuser/{userId} [put]
func UpdateSysUser(c *gin.Context) {
var data models.SysUser
err := c.Bind(&data)
tools.HasError(err, "数据解析失败", -1)
data.UpdateBy = tools.GetUserIdStr(c)
result, err := data.Update(data.UserId)
tools.HasError(err, "修改失败", 500)
app.OK(c, result, "修改成功")
}
// @Summary 删除用户数据
// @Description 删除数据
// @Tags 用户
// @Param userId path int true "userId"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/sysuser/{userId} [delete]
func DeleteSysUser(c *gin.Context) {
var data models.SysUser
data.UpdateBy = tools.GetUserIdStr(c)
IDS := tools.IdsStrToIdsIntGroup("userId", c)
result, err := data.BatchDelete(IDS)
tools.HasError(err, "删除失败", 500)
app.OK(c, result, "删除成功")
}
// @Summary 修改头像
// @Description 获取JSON
// @Tags 用户
// @Accept multipart/form-data
// @Param file formData file true "file"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/user/profileAvatar [post]
func InsetSysUserAvatar(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["upload[]"]
guid := uuid.New().String()
filPath := "static/uploadfile/" + guid + ".jpg"
for _, file := range files {
global.Logger.Debug(file.Filename)
// 上传文件至指定目录
_ = c.SaveUploadedFile(file, filPath)
}
sysuser := models.SysUser{}
sysuser.UserId = tools.GetUserId(c)
sysuser.Avatar = "/" + filPath
sysuser.UpdateBy = tools.GetUserIdStr(c)
sysuser.Update(sysuser.UserId)
app.OK(c, filPath, "修改成功")
}
func SysUserUpdatePwd(c *gin.Context) {
var pwd models.SysUserPwd
err := c.Bind(&pwd)
tools.HasError(err, "数据解析失败", 500)
sysuser := models.SysUser{}
sysuser.UserId = tools.GetUserId(c)
sysuser.SetPwd(pwd)
app.OK(c, "", "密码修改成功")
}
-48
View File
@@ -1,48 +0,0 @@
package tools
import (
"github.com/gin-gonic/gin"
"go-admin/models/tools"
tools2 "go-admin/tools"
"go-admin/tools/app"
"net/http"
)
// @Summary 分页列表数据 / page list data
// @Description 数据库表列分页列表 / database table column page list
// @Tags 工具 / Tools
// @Param tableName query string false "tableName / 数据表名称"
// @Param pageSize query int false "pageSize / 页条数"
// @Param pageIndex query int false "pageIndex / 页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/db/columns/page [get]
func GetDBColumnList(c *gin.Context) {
var data tools.DBColumns
var err error
var pageSize = 10
var pageIndex = 1
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize = tools2.StrToInt(err, size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex = tools2.StrToInt(err, index)
}
data.TableName = c.Request.FormValue("tableName")
tools2.Assert(data.TableName == "", "table name cannot be empty!", 500)
result, count, err := data.GetPage(pageSize, pageIndex)
tools2.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
-53
View File
@@ -1,53 +0,0 @@
package tools
import (
"github.com/gin-gonic/gin"
"go-admin/models/tools"
tools2 "go-admin/tools"
"go-admin/tools/app"
"go-admin/tools/config"
"net/http"
)
// @Summary 分页列表数据 / page list data
// @Description 数据库表分页列表 / database table page list
// @Tags 工具 / Tools
// @Param tableName query string false "tableName / 数据表名称"
// @Param pageSize query int false "pageSize / 页条数"
// @Param pageIndex query int false "pageIndex / 页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/db/tables/page [get]
func GetDBTableList(c *gin.Context) {
var res app.Response
var data tools.DBTables
var err error
var pageSize = 10
var pageIndex = 1
if config.DatabaseConfig.Driver == "sqlite3" || config.DatabaseConfig.Driver == "postgres" {
res.Msg = "对不起,sqlite3 或 postgres 不支持代码生成!"
c.JSON(http.StatusOK, res.ReturnError(500))
return
}
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize = tools2.StrToInt(err, size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex = tools2.StrToInt(err, index)
}
data.TableName = c.Request.FormValue("tableName")
result, count, err := data.GetPage(pageSize, pageIndex)
tools2.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
-366
View File
@@ -1,366 +0,0 @@
package tools
import (
"bytes"
"github.com/gin-gonic/gin"
"go-admin/global"
"go-admin/models"
"go-admin/models/tools"
tools2 "go-admin/tools"
"go-admin/tools/app"
"go-admin/tools/config"
"net/http"
"text/template"
)
func Preview(c *gin.Context) {
table := tools.SysTables{}
id, err := tools2.StringToInt(c.Param("tableId"))
tools2.HasError(err, "", -1)
table.TableId = id
t1, err := template.ParseFiles("template/model.go.template")
tools2.HasError(err, "", -1)
t2, err := template.ParseFiles("template/api.go.template")
tools2.HasError(err, "", -1)
t3, err := template.ParseFiles("template/js.go.template")
tools2.HasError(err, "", -1)
t4, err := template.ParseFiles("template/vue.go.template")
tools2.HasError(err, "", -1)
t5, err := template.ParseFiles("template/router.go.template")
tools2.HasError(err, "", -1)
tab, _ := table.Get()
var b1 bytes.Buffer
err = t1.Execute(&b1, tab)
var b2 bytes.Buffer
err = t2.Execute(&b2, tab)
var b3 bytes.Buffer
err = t3.Execute(&b3, tab)
var b4 bytes.Buffer
err = t4.Execute(&b4, tab)
var b5 bytes.Buffer
err = t5.Execute(&b5, tab)
mp := make(map[string]interface{})
mp["template/model.go.template"] = b1.String()
mp["template/api.go.template"] = b2.String()
mp["template/js.go.template"] = b3.String()
mp["template/vue.go.template"] = b4.String()
mp["template/router.go.template"] = b5.String()
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
func GenCode(c *gin.Context) {
table := tools.SysTables{}
id, err := tools2.StringToInt(c.Param("tableId"))
tools2.HasError(err, "", -1)
table.TableId = id
tab, _ := table.Get()
ischeckrole := true
rouyerfile := "template/routercheckrole.go.template"
if c.Param("ischeckrole") != "" {
ischeckrole, err = tools2.StringToBool(c.Query("ischeckrole"))
if err != nil {
ischeckrole = true
}
}
oldTest := "// {{认证路由自动补充在此处请勿删除}}"
newText := "// {{认证路由自动补充在此处请勿删除}} \r\n register" + tab.ClassName + "Router(v1,authMiddleware)"
if !ischeckrole {
oldTest = "// {{无需认证路由自动补充在此处请勿删除}}"
newText = "// {{无需认证路由自动补充在此处请勿删除}} \r\n register" + tab.ClassName + "Router(v1)"
rouyerfile = "template/routernocheckrole.go.template"
}
t1, err := template.ParseFiles("template/model.go.template")
tools2.HasError(err, "", -1)
t2, err := template.ParseFiles("template/api.go.template")
tools2.HasError(err, "", -1)
t3, err := template.ParseFiles(rouyerfile)
tools2.HasError(err, "", -1)
t4, err := template.ParseFiles("template/js.go.template")
tools2.HasError(err, "", -1)
t5, err := template.ParseFiles("template/vue.go.template")
tools2.HasError(err, "", -1)
_ = tools2.PathCreate("./apis/" + tab.ModuleName + "/")
_ = tools2.PathCreate("./models/")
_ = tools2.PathCreate("./router/")
_ = tools2.PathCreate(config.GenConfig.FrontPath + "/api/")
_ = tools2.PathCreate(config.GenConfig.FrontPath + "/views/" + tab.PackageName)
var b1 bytes.Buffer
err = t1.Execute(&b1, tab)
var b2 bytes.Buffer
err = t2.Execute(&b2, tab)
var b3 bytes.Buffer
err = t3.Execute(&b3, tab)
var b4 bytes.Buffer
err = t4.Execute(&b4, tab)
var b5 bytes.Buffer
err = t5.Execute(&b5, tab)
tools2.FileCreate(b1, "./models/"+tab.PackageName+".go")
tools2.FileCreate(b2, "./apis/"+tab.ModuleName+"/"+tab.PackageName+".go")
tools2.FileCreate(b3, "./router/"+tab.PackageName+".go")
tools2.FileCreate(b4, config.GenConfig.FrontPath+"/api/"+tab.PackageName+".js")
tools2.FileCreate(b5, config.GenConfig.FrontPath+"/views/"+tab.PackageName+"/index.vue")
helper := tools2.ReplaceHelper{
Root: "./router/router.go",
OldText: oldTest,
NewText: newText,
}
if helper.OldText == helper.NewText {
global.Logger.Println("error !! the NewText isEqual the OldText")
return
}
if err := helper.DoWrok(); err != nil {
global.Logger.Print("error:", err.Error())
} else {
global.Logger.Print("done!")
}
app.OK(c, "", "Code generated successfully!")
}
func GenMenuAndApi(c *gin.Context) {
table := tools.SysTables{}
timeNow := tools2.GetCurrentTime()
id, err := tools2.StringToInt(c.Param("tableId"))
tools2.HasError(err, "", -1)
table.TableId = id
tab, _ := table.Get()
Mmenu := models.Menu{}
Mmenu.MenuName = tab.TBName + "管理"
Mmenu.Title = tab.TableComment
Mmenu.Icon = "pass"
Mmenu.Path = "/" + tab.TBName
Mmenu.MenuType = "M"
Mmenu.Action = "无"
Mmenu.ParentId = 0
Mmenu.NoCache = false
Mmenu.Component = "Layout"
Mmenu.Sort = 0
Mmenu.Visible = "0"
Mmenu.IsFrame = "0"
Mmenu.CreateBy = "1"
Mmenu.UpdateBy = "1"
Mmenu.CreatedAt = timeNow
Mmenu.UpdatedAt = timeNow
Mmenu.MenuId, err = Mmenu.Create()
Cmenu := models.Menu{}
Cmenu.MenuName = tab.TBName + "管理"
Cmenu.Title = tab.TableComment
Cmenu.Icon = "pass"
Cmenu.Path = tab.TBName
Cmenu.MenuType = "C"
Cmenu.Action = "无"
Cmenu.Permission = tab.PackageName + ":" + tab.ModuleName + ":list"
Cmenu.ParentId = Mmenu.MenuId
Cmenu.NoCache = false
Cmenu.Component = "/" + tab.ModuleName + "/index"
Cmenu.Sort = 0
Cmenu.Visible = "0"
Cmenu.IsFrame = "0"
Cmenu.CreateBy = "1"
Cmenu.UpdateBy = "1"
Cmenu.CreatedAt = timeNow
Cmenu.UpdatedAt = timeNow
Cmenu.MenuId, err = Cmenu.Create()
MList := models.Menu{}
MList.MenuName = tab.TBName
MList.Title = "分页获取" + tab.TableComment
MList.Icon = "pass"
MList.Path = tab.TBName
MList.MenuType = "F"
MList.Action = "无"
MList.Permission = tab.PackageName + ":" + tab.ModuleName + ":query"
MList.ParentId = Cmenu.MenuId
MList.NoCache = false
MList.Sort = 0
MList.Visible = "0"
MList.IsFrame = "0"
MList.CreateBy = "1"
MList.UpdateBy = "1"
MList.CreatedAt = timeNow
MList.UpdatedAt = timeNow
MList.MenuId, err = MList.Create()
MCreate := models.Menu{}
MCreate.MenuName = tab.TBName
MCreate.Title = "创建" + tab.TableComment
MCreate.Icon = "pass"
MCreate.Path = tab.TBName
MCreate.MenuType = "F"
MCreate.Action = "无"
MCreate.Permission = tab.PackageName + ":" + tab.ModuleName + ":add"
MCreate.ParentId = Cmenu.MenuId
MCreate.NoCache = false
MCreate.Sort = 0
MCreate.Visible = "0"
MCreate.IsFrame = "0"
MCreate.CreateBy = "1"
MCreate.UpdateBy = "1"
MCreate.CreatedAt = timeNow
MCreate.UpdatedAt = timeNow
MCreate.MenuId, err = MCreate.Create()
MUpdate := models.Menu{}
MUpdate.MenuName = tab.TBName
MUpdate.Title = "修改" + tab.TableComment
MUpdate.Icon = "pass"
MUpdate.Path = tab.TBName
MUpdate.MenuType = "F"
MUpdate.Action = "无"
MUpdate.Permission = tab.PackageName + ":" + tab.ModuleName + ":edit"
MUpdate.ParentId = Cmenu.MenuId
MUpdate.NoCache = false
MUpdate.Sort = 0
MUpdate.Visible = "0"
MUpdate.IsFrame = "0"
MUpdate.CreateBy = "1"
MUpdate.UpdateBy = "1"
MUpdate.CreatedAt = timeNow
MUpdate.UpdatedAt = timeNow
MUpdate.MenuId, err = MUpdate.Create()
MDelete := models.Menu{}
MDelete.MenuName = tab.TBName
MDelete.Title = "删除" + tab.TableComment
MDelete.Icon = "pass"
MDelete.Path = tab.TBName
MDelete.MenuType = "F"
MDelete.Action = "无"
MDelete.Permission = tab.PackageName + ":" + tab.ModuleName + ":remove"
MDelete.ParentId = Cmenu.MenuId
MDelete.NoCache = false
MDelete.Sort = 0
MDelete.Visible = "0"
MDelete.IsFrame = "0"
MDelete.CreateBy = "1"
MDelete.UpdateBy = "1"
MDelete.CreatedAt = timeNow
MDelete.UpdatedAt = timeNow
MDelete.MenuId, err = MDelete.Create()
var InterfaceId = 63
Amenu := models.Menu{}
Amenu.MenuName = tab.TBName
Amenu.Title = tab.TableComment
Amenu.Icon = "bug"
Amenu.Path = tab.TBName
Amenu.MenuType = "M"
Amenu.Action = "无"
Amenu.ParentId = InterfaceId
Amenu.NoCache = false
Amenu.Sort = 0
Amenu.Visible = "1"
Amenu.IsFrame = "0"
Amenu.CreateBy = "1"
Amenu.UpdateBy = "1"
Amenu.CreatedAt = timeNow
Amenu.UpdatedAt = timeNow
Amenu.MenuId, err = Amenu.Create()
AList := models.Menu{}
AList.MenuName = tab.TBName
AList.Title = "分页获取" + tab.TableComment
AList.Icon = "bug"
AList.Path = "/api/v1/" + tab.ModuleName + "List"
AList.MenuType = "A"
AList.Action = "GET"
AList.ParentId = Amenu.MenuId
AList.NoCache = false
AList.Sort = 0
AList.Visible = "1"
AList.IsFrame = "0"
AList.CreateBy = "1"
AList.UpdateBy = "1"
AList.CreatedAt = timeNow
AList.UpdatedAt = timeNow
AList.MenuId, err = AList.Create()
AGet := models.Menu{}
AGet.MenuName = tab.TBName
AGet.Title = "根据id获取" + tab.TableComment
AGet.Icon = "bug"
AGet.Path = "/api/v1/" + tab.ModuleName + "/:id"
AGet.MenuType = "A"
AGet.Action = "GET"
AGet.ParentId = Amenu.MenuId
AGet.NoCache = false
AGet.Sort = 0
AGet.Visible = "1"
AGet.IsFrame = "0"
AGet.CreateBy = "1"
AGet.UpdateBy = "1"
AGet.CreatedAt = timeNow
AGet.UpdatedAt = timeNow
AGet.MenuId, err = AGet.Create()
ACreate := models.Menu{}
ACreate.MenuName = tab.TBName
ACreate.Title = "创建" + tab.TableComment
ACreate.Icon = "bug"
ACreate.Path = "/api/v1/" + tab.ModuleName
ACreate.MenuType = "A"
ACreate.Action = "POST"
ACreate.ParentId = Amenu.MenuId
ACreate.NoCache = false
ACreate.Sort = 0
ACreate.Visible = "1"
ACreate.IsFrame = "0"
ACreate.CreateBy = "1"
ACreate.UpdateBy = "1"
ACreate.CreatedAt = timeNow
ACreate.UpdatedAt = timeNow
ACreate.MenuId, err = ACreate.Create()
AUpdate := models.Menu{}
AUpdate.MenuName = tab.TBName
AUpdate.Title = "修改" + tab.TableComment
AUpdate.Icon = "bug"
AUpdate.Path = "/api/v1/" + tab.ModuleName
AUpdate.MenuType = "A"
AUpdate.Action = "PUT"
AUpdate.ParentId = Amenu.MenuId
AUpdate.NoCache = false
AUpdate.Sort = 0
AUpdate.Visible = "1"
AUpdate.IsFrame = "0"
AUpdate.CreateBy = "1"
AUpdate.UpdateBy = "1"
AUpdate.CreatedAt = timeNow
AUpdate.UpdatedAt = timeNow
AUpdate.MenuId, err = AUpdate.Create()
ADelete := models.Menu{}
ADelete.MenuName = tab.TBName
ADelete.Title = "删除" + tab.TableComment
ADelete.Icon = "bug"
ADelete.Path = "/api/v1/" + tab.ModuleName + "/:id"
ADelete.MenuType = "A"
ADelete.Action = "DELETE"
ADelete.ParentId = Amenu.MenuId
ADelete.NoCache = false
ADelete.Sort = 0
ADelete.Visible = "1"
ADelete.IsFrame = "0"
ADelete.CreateBy = "1"
ADelete.UpdateBy = "1"
ADelete.CreatedAt = timeNow
ADelete.UpdatedAt = timeNow
ADelete.MenuId, err = ADelete.Create()
app.OK(c, "", "数据生成成功!")
}
-222
View File
@@ -1,222 +0,0 @@
package tools
import (
"github.com/gin-gonic/gin"
"go-admin/models/tools"
tools2 "go-admin/tools"
"go-admin/tools/app"
"net/http"
"strings"
)
// @Summary 分页列表数据
// @Description 生成表分页列表
// @Tags 工具 - 生成表
// @Param tableName query string false "tableName / 数据表名称"
// @Param pageSize query int false "pageSize / 页条数"
// @Param pageIndex query int false "pageIndex / 页码"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys/tables/page [get]
func GetSysTableList(c *gin.Context) {
var data tools.SysTables
var err error
var pageSize = 10
var pageIndex = 1
if size := c.Request.FormValue("pageSize"); size != "" {
pageSize, err = tools2.StringToInt(size)
}
if index := c.Request.FormValue("pageIndex"); index != "" {
pageIndex, err = tools2.StringToInt(index)
}
data.TBName = c.Request.FormValue("tableName")
data.TableComment = c.Request.FormValue("tableComment")
result, count, err := data.GetPage(pageSize, pageIndex)
tools2.HasError(err, "", -1)
var mp = make(map[string]interface{}, 3)
mp["list"] = result
mp["count"] = count
mp["pageIndex"] = pageIndex
mp["pageSize"] = pageSize
var res app.Response
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 获取配置
// @Description 获取JSON
// @Tags 工具 - 生成表
// @Param configKey path int true "configKey"
// @Success 200 {object} app.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys/tables/info/{tableId} [get]
// @Security Bearer
func GetSysTables(c *gin.Context) {
var data tools.SysTables
data.TableId, _ = tools2.StringToInt(c.Param("tableId"))
result, err := data.Get()
tools2.HasError(err, "抱歉未找到相关信息", -1)
var res app.Response
res.Data = result
mp := make(map[string]interface{})
mp["rows"] = result.Columns
mp["info"] = result
res.Data = mp
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 添加表结构
// @Description 添加表结构
// @Tags 工具 - 生成表
// @Accept application/json
// @Product application/json
// @Param tables query string false "tableName / 数据表名称"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/sys/tables/info [post]
// @Security Bearer
func InsertSysTable(c *gin.Context) {
tablesList := strings.Split(c.Request.FormValue("tables"), ",")
for i := 0; i < len(tablesList); i++ {
data, err := genTableInit(tablesList, i, c)
_, err = data.Create()
tools2.HasError(err, "", -1)
}
var res app.Response
res.Msg = "添加成功!"
c.JSON(http.StatusOK, res.ReturnOK())
}
func genTableInit(tablesList []string, i int, c *gin.Context) (tools.SysTables, error) {
var data tools.SysTables
var dbTable tools.DBTables
var dbColumn tools.DBColumns
data.TBName = tablesList[i]
data.CreateBy = tools2.GetUserIdStr(c)
dbTable.TableName = data.TBName
dbtable, err := dbTable.Get()
dbColumn.TableName = data.TBName
tablenamelist := strings.Split(dbColumn.TableName, "_")
for i := 0; i < len(tablenamelist); i++ {
strStart := string([]byte(tablenamelist[i])[:1])
strend := string([]byte(tablenamelist[i])[1:])
data.ClassName += strings.ToUpper(strStart) + strend
data.PackageName += strings.ToLower(strStart) + strings.ToLower(strend)
data.ModuleName += strings.ToLower(strStart) + strings.ToLower(strend)
}
data.TplCategory = "crud"
data.Crud = true
dbcolumn, err := dbColumn.GetList()
data.CreateBy = tools2.GetUserIdStr(c)
data.TableComment = dbtable.TableComment
if dbtable.TableComment == "" {
data.TableComment = data.ClassName
}
data.FunctionName = data.TableComment
data.BusinessName = data.ModuleName
data.IsLogicalDelete = "1"
data.LogicalDelete = true
data.LogicalDeleteColumn = "is_del"
data.FunctionAuthor = "wenjianzhang"
for i := 0; i < len(dbcolumn); i++ {
var column tools.SysColumns
column.ColumnComment = dbcolumn[i].ColumnComment
column.ColumnName = dbcolumn[i].ColumnName
column.ColumnType = dbcolumn[i].ColumnType
column.Sort = i + 1
column.Insert = true
column.IsInsert = "1"
column.QueryType = "EQ"
column.IsPk = "0"
namelist := strings.Split(dbcolumn[i].ColumnName, "_")
for i := 0; i < len(namelist); i++ {
strStart := string([]byte(namelist[i])[:1])
strend := string([]byte(namelist[i])[1:])
column.GoField += strings.ToUpper(strStart) + strend
if i == 0 {
column.JsonField = strings.ToLower(strStart) + strend
} else {
column.JsonField += strings.ToUpper(strStart) + strend
}
}
if strings.Contains(dbcolumn[i].ColumnKey, "PR") {
column.IsPk = "1"
column.Pk = true
data.PkColumn = dbcolumn[i].ColumnName
data.PkGoField = column.GoField
data.PkJsonField = column.JsonField
}
column.IsRequired = "0"
if strings.Contains(dbcolumn[i].IsNullable, "NO") {
column.IsRequired = "1"
column.Required = true
}
if strings.Contains(dbcolumn[i].ColumnType, "int") {
column.GoType = "int"
column.HtmlType = "input"
} else {
column.GoType = "string"
column.HtmlType = "input"
}
data.Columns = append(data.Columns, column)
}
return data, err
}
// @Summary 修改表结构
// @Description 修改表结构
// @Tags 工具 - 生成表
// @Accept application/json
// @Product application/json
// @Param data body tools.SysTables true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/sys/tables/info [put]
// @Security Bearer
func UpdateSysTable(c *gin.Context) {
var data tools.SysTables
err := c.Bind(&data)
tools2.HasError(err, "数据解析失败", -1)
data.UpdateBy = tools2.GetUserIdStr(c)
result, err := data.Update()
tools2.HasError(err, "", -1)
var res app.Response
res.Data = result
res.Msg = "修改成功"
c.JSON(http.StatusOK, res.ReturnOK())
}
// @Summary 删除表结构
// @Description 删除表结构
// @Tags 工具 - 生成表
// @Param tableId path int true "tableId"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/sys/tables/info/{tableId} [delete]
func DeleteSysTables(c *gin.Context) {
var data tools.SysTables
IDS := tools2.IdsStrToIdsIntGroup("tableId", c)
_, err := data.BatchDelete(IDS)
tools2.HasError(err, "删除失败", 500)
var res app.Response
res.Msg = "删除成功"
c.JSON(http.StatusOK, res.ReturnOK())
}
+40
View File
@@ -0,0 +1,40 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/v2/captcha"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
)
type System struct {
api.Api
}
// GenerateCaptchaHandler 获取验证码
// @Summary 获取验证码
// @Description 获取验证码
// @Tags 登陆
// @Success 200 {object} response.Response{data=string,id=string,msg=string} "{"code": 200, "data": [...]}"
// @Router /api/v1/captcha [get]
func (e System) GenerateCaptchaHandler(c *gin.Context) {
if err := e.MakeContext(c).Errors; err != nil {
e.Error(500, err, "服务初始化失败!")
return
}
// The answer is deliberately discarded rather than logged. It used to be
// written at info level, which put a currently valid captcha answer in the
// application log - anyone able to read the log could bypass the check the
// captcha exists to enforce.
id, b64s, _, err := captcha.DriverDigitFunc()
if err != nil {
e.Logger.Errorf("DriverDigitFunc error, %s", err.Error())
e.Error(500, err, "验证码获取失败")
return
}
e.Custom(gin.H{
"code": 200,
"data": b64s,
"id": id,
"msg": "success",
})
}
@@ -1,4 +1,4 @@
package system
package apis
import (
"github.com/gin-gonic/gin"
@@ -11,13 +11,14 @@ const INDEX = `
<meta charset="utf-8">
<title>GO-ADMIN欢迎您</title>
<style>
body{
margin:0;
padding:0;
overflow-y:hidden
html,body{
margin:0;
padding:0;
height:100%;
overflow-y:hidden;
}
</style>
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
<script src="https://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
<script type="text/javascript">
window.onerror=function(){return true;}
$(function(){
@@ -28,12 +29,12 @@ $(function(){
</script>
</head>
<body>
<iframe id="iframe" frameborder="0" src="http://doc.zhangwj.com/go-admin-site/" style="width:100%;"></iframe>
<iframe id="iframe" frameborder="0" src="https://www.go-admin.pro" style="width:100%;height:100%;"></iframe>
</body>
</html>
`
func HelloWorld(c *gin.Context) {
func GoAdmin(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(200, INDEX)
}
+148
View File
@@ -0,0 +1,148 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/v2/response"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
"go-admin/common/actions"
)
type SysApi struct {
api.Api
}
// GetPage 获取接口管理列表
// @Summary 获取接口管理列表
// @Description 获取接口管理列表
// @Tags 接口管理
// @Param name query string false "名称"
// @Param title query string false "标题"
// @Param path query string false "地址"
// @Param action query string false "类型"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-api [get]
// @Security Bearer
func (e SysApi) GetPage(c *gin.Context) {
s := service.SysApi{}
req := dto.SysApiGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
//数据权限检查
p := actions.GetPermissionFromContext(c)
list := make([]models.SysApi, 0)
var count int64
err = s.GetPage(&req, p, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 获取接口管理
// @Summary 获取接口管理
// @Description 获取接口管理
// @Tags 接口管理
// @Param id path string false "id"
// @Success 200 {object} response.Response{data=models.SysApi} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-api/{id} [get]
// @Security Bearer
func (e SysApi) Get(c *gin.Context) {
req := dto.SysApiGetReq{}
s := service.SysApi{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
p := actions.GetPermissionFromContext(c)
var object models.SysApi
err = s.Get(&req, p, &object).Error
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Update 修改接口管理
// @Summary 修改接口管理
// @Description 修改接口管理
// @Tags 接口管理
// @Accept application/json
// @Product application/json
// @Param data body dto.SysApiUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/sys-api/{id} [put]
// @Security Bearer
func (e SysApi) Update(c *gin.Context) {
req := dto.SysApiUpdateReq{}
s := service.SysApi{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
req.SetUpdateBy(user.GetUserId(c))
p := actions.GetPermissionFromContext(c)
err = s.Update(&req, p)
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// DeleteSysApi 删除接口管理
// @Summary 删除接口管理
// @Description 删除接口管理
// @Tags 接口管理
// @Param data body dto.SysApiDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
// @Router /api/v1/sys-api [delete]
// @Security Bearer
func (e SysApi) DeleteSysApi(c *gin.Context) {
req := dto.SysApiDeleteReq{}
s := service.SysApi{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
p := actions.GetPermissionFromContext(c)
err = s.Remove(&req, p)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
+313
View File
@@ -0,0 +1,313 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysConfig struct {
api.Api
}
// GetPage 获取配置管理列表
// @Summary 获取配置管理列表
// @Description 获取配置管理列表
// @Tags 配置管理
// @Param configName query string false "名称"
// @Param configKey query string false "key"
// @Param configType query string false "类型"
// @Param isFrontend query int false "是否前端"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-config [get]
// @Security Bearer
func (e SysConfig) GetPage(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
list := make([]models.SysConfig, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 获取配置管理
// @Summary 获取配置管理
// @Description 获取配置管理
// @Tags 配置管理
// @Param id path string false "id"
// @Success 200 {object} response.Response{data=models.SysConfig} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-config/{id} [get]
// @Security Bearer
func (e SysConfig) Get(c *gin.Context) {
req := dto.SysConfigGetReq{}
s := service.SysConfig{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysConfig
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(object, "查询成功")
}
// Insert 创建配置管理
// @Summary 创建配置管理
// @Description 创建配置管理
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Param data body dto.SysConfigControl true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "创建成功"}"
// @Router /api/v1/sys-config [post]
// @Security Bearer
func (e SysConfig) Insert(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigControl{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update 修改配置管理
// @Summary 修改配置管理
// @Description 修改配置管理
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Param data body dto.SysConfigControl true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/sys-config/{id} [put]
// @Security Bearer
func (e SysConfig) Update(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigControl{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete 删除配置管理
// @Summary 删除配置管理
// @Description 删除配置管理
// @Tags 配置管理
// @Param ids body []int false "ids"
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
// @Router /api/v1/sys-config [delete]
// @Security Bearer
func (e SysConfig) Delete(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
// Get2SysApp 获取系统配置信息
// @Summary 获取系统前台配置信息,主要注意这里不在验证权限
// @Description 获取系统配置信息,主要注意这里不在验证权限
// @Tags 配置管理
// @Success 200 {object} response.Response{data=map[string]string} "{"code": 200, "data": [...]}"
// @Router /api/v1/app-config [get]
func (e SysConfig) Get2SysApp(c *gin.Context) {
req := dto.SysConfigGetToSysAppReq{}
s := service.SysConfig{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
// 控制只读前台的数据
req.IsFrontend = "1"
list := make([]models.SysConfig, 0)
err = s.GetWithKeyList(&req, &list)
if err != nil {
e.Error(500, err, "查询失败")
return
}
mp := make(map[string]string)
for i := 0; i < len(list); i++ {
key := list[i].ConfigKey
if key != "" {
mp[key] = list[i].ConfigValue
}
}
e.OK(mp, "查询成功")
}
// Get2Set 获取配置
// @Summary 获取配置
// @Description 界面操作设置配置值的获取
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Success 200 {object} response.Response{data=map[string]interface{}} "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/set-config [get]
// @Security Bearer
func (e SysConfig) Get2Set(c *gin.Context) {
s := service.SysConfig{}
req := make([]dto.GetSetSysConfigReq, 0)
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.GetForSet(&req)
if err != nil {
e.Error(500, err, "查询失败")
return
}
m := make(map[string]interface{}, 0)
for _, v := range req {
m[v.ConfigKey] = v.ConfigValue
}
e.OK(m, "查询成功")
}
// Update2Set 设置配置
// @Summary 设置配置
// @Description 界面操作设置配置值
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Param data body []dto.GetSetSysConfigReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/set-config [put]
// @Security Bearer
func (e SysConfig) Update2Set(c *gin.Context) {
s := service.SysConfig{}
req := make([]dto.GetSetSysConfigReq, 0)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.UpdateForSet(&req)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK("", "更新成功")
}
// GetSysConfigByKEYForService 根据Key获取SysConfig的Service
// @Summary 根据Key获取SysConfig的Service
// @Description 根据Key获取SysConfig的Service
// @Tags 配置管理
// @Param configKey path string false "configKey"
// @Success 200 {object} response.Response{data=dto.SysConfigByKeyReq} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-config/{id} [get]
// @Security Bearer
func (e SysConfig) GetSysConfigByKEYForService(c *gin.Context) {
var s = new(service.SysConfig)
var req = new(dto.SysConfigByKeyReq)
var resp = new(dto.GetSysConfigByKEYForServiceResp)
err := e.MakeContext(c).
MakeOrm().
Bind(req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.GetWithKey(req, resp)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(resp, s.Msg)
}
+238
View File
@@ -0,0 +1,238 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/v2/response"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysDept struct {
api.Api
}
// GetPage
// @Summary 分页部门列表数据
// @Description 分页列表
// @Tags 部门
// @Param deptName query string false "deptName"
// @Param deptId query string false "deptId"
// @Param position query string false "position"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dept [get]
// @Security Bearer
func (e SysDept) GetPage(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDept, 0)
list, err = s.SetDeptPage(&req)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(list, "查询成功")
}
// Get
// @Summary 获取部门数据
// @Description 获取JSON
// @Tags 部门
// @Param deptId path string false "deptId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dept/{deptId} [get]
// @Security Bearer
func (e SysDept) Get(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysDept
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert 添加部门
// @Summary 添加部门
// @Description 获取JSON
// @Tags 部门
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDeptInsertReq true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dept [post]
// @Security Bearer
func (e SysDept) Insert(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置创建人
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改部门
// @Description 获取JSON
// @Tags 部门
// @Accept application/json
// @Product application/json
// @Param id path int true "id"
// @Param data body dto.SysDeptUpdateReq true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dept/{deptId} [put]
// @Security Bearer
func (e SysDept) Update(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除部门
// @Description 删除数据
// @Tags 部门
// @Param data body dto.SysDeptDeleteReq true "body"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/dept [delete]
// @Security Bearer
func (e SysDept) Delete(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
// Get2Tree 用户管理 左侧部门树
func (e SysDept) Get2Tree(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]dto.DeptLabel, 0)
list, err = s.SetDeptTree(&req)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(list, "")
}
// GetDeptTreeRoleSelect TODO: 此接口需要调整不应该将list和选中放在一起
func (e SysDept) GetDeptTreeRoleSelect(c *gin.Context) {
s := service.SysDept{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
id, err := pkg.StringToInt(c.Param("roleId"))
result, err := s.SetDeptLabel()
if err != nil {
e.Error(500, err, err.Error())
return
}
menuIds := make([]int, 0)
if id != 0 {
menuIds, err = s.GetWithRoleId(id)
if err != nil {
e.Error(500, err, err.Error())
return
}
}
e.OK(gin.H{
"depts": result,
"checkedKeys": menuIds,
}, "")
}
+220
View File
@@ -0,0 +1,220 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/v2/response"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysDictData struct {
api.Api
}
// GetPage
// @Summary 字典数据列表
// @Description 获取JSON
// @Tags 字典数据
// @Param status query string false "status"
// @Param dictCode query string false "dictCode"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/data [get]
// @Security Bearer
func (e SysDictData) GetPage(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictData, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 通过编码获取字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Param dictCode path int true "字典编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/data/{dictCode} [get]
// @Security Bearer
func (e SysDictData) Get(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysDictData
err = s.Get(&req, &object)
if err != nil {
e.Logger.Warnf("Get error: %s", err.Error())
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 添加字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictDataInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}"
// @Router /api/v1/dict/data [post]
// @Security Bearer
func (e SysDictData) Insert(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictDataUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/dict/data/{dictCode} [put]
// @Security Bearer
func (e SysDictData) Update(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除字典数据
// @Description 删除数据
// @Tags 字典数据
// @Param dictCode body dto.SysDictDataDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
// @Router /api/v1/dict/data [delete]
// @Security Bearer
func (e SysDictData) Delete(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
// GetAll 数据字典根据key获取 业务页面使用
// @Summary 数据字典根据key获取
// @Description 数据字典根据key获取
// @Tags 字典数据
// @Param dictType query int true "dictType"
// @Success 200 {object} response.Response{data=[]dto.SysDictDataGetAllResp} "{"code": 200, "data": [...]}"
// @Router /api/v1/dict-data/option-select [get]
// @Security Bearer
func (e SysDictData) GetAll(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictData, 0)
err = s.GetAll(&req, &list)
if err != nil {
e.Error(500, err, "查询失败")
return
}
l := make([]dto.SysDictDataGetAllResp, 0)
for _, i := range list {
d := dto.SysDictDataGetAllResp{}
e.Translate(i, &d)
l = append(l, d)
}
e.OK(l,"查询成功")
}
+210
View File
@@ -0,0 +1,210 @@
package apis
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/v2/response"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysDictType struct {
api.Api
}
// GetPage 字典类型列表数据
// @Summary 字典类型列表数据
// @Description 获取JSON
// @Tags 字典类型
// @Param dictName query string false "dictName"
// @Param dictId query string false "dictId"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type [get]
// @Security Bearer
func (e SysDictType) GetPage(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictType, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 字典类型通过字典id获取
// @Summary 字典类型通过字典id获取
// @Description 获取JSON
// @Tags 字典类型
// @Param dictId path int true "字典类型编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type/{dictId} [get]
// @Security Bearer
func (e SysDictType) Get(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysDictType
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
//Insert 字典类型创建
// @Summary 添加字典类型
// @Description 获取JSON
// @Tags 字典类型
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictTypeInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type [post]
// @Security Bearer
func (e SysDictType) Insert(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Logger.Error(err)
e.Error(500, err,fmt.Sprintf(" 创建字典类型失败,详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改字典类型
// @Description 获取JSON
// @Tags 字典类型
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictTypeUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type/{dictId} [put]
// @Security Bearer
func (e SysDictType) Update(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Error(500, err, err.Error())
e.Logger.Error(err)
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除字典类型
// @Description 删除数据
// @Tags 字典类型
// @Param dictCode body dto.SysDictTypeDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type [delete]
// @Security Bearer
func (e SysDictType) Delete(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(req.GetId(), "删除成功")
}
// GetAll
// @Summary 字典类型全部数据 代码生成使用接口
// @Description 获取JSON
// @Tags 字典类型
// @Param dictName query string false "dictName"
// @Param dictId query string false "dictId"
// @Param dictType query string false "dictType"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type-option-select [get]
// @Security Bearer
func (e SysDictType) GetAll(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictType, 0)
err = s.GetAll(&req, &list)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(list, "查询成功")
}
+110
View File
@@ -0,0 +1,110 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysLoginLog struct {
api.Api
}
// GetPage 登录日志列表
// @Summary 登录日志列表
// @Description 获取JSON
// @Tags 登录日志
// @Param username query string false "用户名"
// @Param ipaddr query string false "ip地址"
// @Param loginLocation query string false "归属地"
// @Param status query string false "状态"
// @Param beginTime query string false "开始时间"
// @Param endTime query string false "结束时间"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-login-log [get]
// @Security Bearer
func (e SysLoginLog) GetPage(c *gin.Context) {
s := service.SysLoginLog{}
req :=dto.SysLoginLogGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysLoginLog, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 登录日志通过id获取
// @Summary 登录日志通过id获取
// @Description 获取JSON
// @Tags 登录日志
// @Param id path string false "id"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-login-log/{id} [get]
// @Security Bearer
func (e SysLoginLog) Get(c *gin.Context) {
s := service.SysLoginLog{}
req :=dto.SysLoginLogGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysLoginLog
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Delete 登录日志删除
// @Summary 登录日志删除
// @Description 登录日志删除
// @Tags 登录日志
// @Param data body dto.SysLoginLogDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-login-log [delete]
// @Security Bearer
func (e SysLoginLog) Delete(c *gin.Context) {
s := service.SysLoginLog{}
req :=dto.SysLoginLogDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
+249
View File
@@ -0,0 +1,249 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysMenu struct {
api.Api
}
// GetPage Menu列表数据
// @Summary Menu列表数据
// @Description 获取JSON
// @Tags 菜单
// @Param menuName query string false "menuName"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu [get]
// @Security Bearer
func (e SysMenu) GetPage(c *gin.Context) {
s := service.SysMenu{}
req := dto.SysMenuGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var list = make([]models.SysMenu, 0)
err = s.GetPage(&req, &list).Error
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(list, "查询成功")
}
// Get 获取菜单详情
// @Summary Menu详情数据
// @Description 获取JSON
// @Tags 菜单
// @Param id path string false "id"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu/{id} [get]
// @Security Bearer
func (e SysMenu) Get(c *gin.Context) {
req := dto.SysMenuGetReq{}
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object = models.SysMenu{}
err = s.Get(&req, &object).Error
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert 创建菜单
// @Summary 创建菜单
// @Description 获取JSON
// @Tags 菜单
// @Accept application/json
// @Product application/json
// @Param data body dto.SysMenuInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu [post]
// @Security Bearer
func (e SysMenu) Insert(c *gin.Context) {
req := dto.SysMenuInsertReq{}
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置创建人
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req).Error
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update 修改菜单
// @Summary 修改菜单
// @Description 获取JSON
// @Tags 菜单
// @Accept application/json
// @Product application/json
// @Param id path int true "id"
// @Param data body dto.SysMenuUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu/{id} [put]
// @Security Bearer
func (e SysMenu) Update(c *gin.Context) {
req := dto.SysMenuUpdateReq{}
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req).Error
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete 删除菜单
// @Summary 删除菜单
// @Description 删除数据
// @Tags 菜单
// @Param data body dto.SysMenuDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu [delete]
// @Security Bearer
func (e SysMenu) Delete(c *gin.Context) {
control := new(dto.SysMenuDeleteReq)
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(control, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(control).Error
if err != nil {
e.Logger.Errorf("RemoveSysMenu error, %s", err)
e.Error(500, err, "删除失败")
return
}
e.OK(control.GetId(), "删除成功")
}
// GetMenuRole 根据登录角色名称获取菜单列表数据(左菜单使用)
// @Summary 根据登录角色名称获取菜单列表数据(左菜单使用)
// @Description 获取JSON
// @Tags 菜单
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menurole [get]
// @Security Bearer
func (e SysMenu) GetMenuRole(c *gin.Context) {
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
result, err := s.SetMenuRole(user.GetRoleName(c))
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(result, "")
}
// GetMenuTreeSelect 根据角色ID查询菜单下拉树结构
// @Summary 角色修改使用的菜单列表
// @Description 获取JSON
// @Tags 菜单
// @Accept application/json
// @Product application/json
// @Param roleId path int true "roleId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menuTreeselect/{roleId} [get]
// @Security Bearer
func (e SysMenu) GetMenuTreeSelect(c *gin.Context) {
m := service.SysMenu{}
r := service.SysRole{}
req := dto.SelectRole{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&m.Service).
MakeService(&r.Service).
Bind(&req, nil).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
result, err := m.SetLabel()
if err != nil {
e.Error(500, err, "查询失败")
return
}
menuIds := make([]int, 0)
if req.RoleId != 0 {
menuIds, err = r.GetRoleMenuId(req.RoleId)
if err != nil {
e.Error(500, err, "")
return
}
}
e.OK(gin.H{
"menus": result,
"checkedKeys": menuIds,
}, "获取成功")
}
+118
View File
@@ -0,0 +1,118 @@
package apis
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysOperaLog struct {
api.Api
}
// GetPage 操作日志列表
// @Summary 操作日志列表
// @Description 获取JSON
// @Tags 操作日志
// @Param title query string false "title"
// @Param method query string false "method"
// @Param requestMethod query string false "requestMethod"
// @Param operUrl query string false "operUrl"
// @Param operIp query string false "operIp"
// @Param status query string false "status"
// @Param beginTime query string false "beginTime"
// @Param endTime query string false "endTime"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-opera-log [get]
// @Security Bearer
func (e SysOperaLog) GetPage(c *gin.Context) {
s := service.SysOperaLog{}
req := new(dto.SysOperaLogGetPageReq)
err := e.MakeContext(c).
MakeOrm().
Bind(req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysOperaLog, 0)
var count int64
err = s.GetPage(req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 操作日志通过id获取
// @Summary 操作日志通过id获取
// @Description 获取JSON
// @Tags 操作日志
// @Param id path string false "id"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-opera-log/{id} [get]
// @Security Bearer
func (e SysOperaLog) Get(c *gin.Context) {
s := new(service.SysOperaLog)
req :=dto.SysOperaLogGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysOperaLog
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Delete 操作日志删除
// DeleteSysMenu 操作日志删除
// @Summary 删除操作日志
// @Description 删除数据
// @Tags 操作日志
// @Param data body dto.SysOperaLogDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-opera-log [delete]
// @Security Bearer
func (e SysOperaLog) Delete(c *gin.Context) {
s := new(service.SysOperaLog)
req :=dto.SysOperaLogDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(&req)
if err != nil {
e.Logger.Error(err)
e.Error(500,err, fmt.Sprintf("删除失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "删除成功")
}
+184
View File
@@ -0,0 +1,184 @@
package apis
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/v2/response"
"go-admin/app/admin/models"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysPost struct {
api.Api
}
// GetPage
// @Summary 岗位列表数据
// @Description 获取JSON
// @Tags 岗位
// @Param postName query string false "postName"
// @Param postCode query string false "postCode"
// @Param postId query string false "postId"
// @Param status query string false "status"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post [get]
// @Security Bearer
func (e SysPost) GetPage(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysPost, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 获取岗位信息
// @Description 获取JSON
// @Tags 岗位
// @Param id path int true "编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post/{postId} [get]
// @Security Bearer
func (e SysPost) Get(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysPost
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, fmt.Sprintf("岗位信息获取失败!错误详情:%s", err.Error()))
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 添加岗位
// @Description 获取JSON
// @Tags 岗位
// @Accept application/json
// @Product application/json
// @Param data body dto.SysPostInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post [post]
// @Security Bearer
func (e SysPost) Insert(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("新建岗位失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改岗位
// @Description 获取JSON
// @Tags 岗位
// @Accept application/json
// @Product application/json
// @Param data body dto.SysPostUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post/{id} [put]
// @Security Bearer
func (e SysPost) Update(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("岗位更新失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除岗位
// @Description 删除数据
// @Tags 岗位
// @Param id body dto.SysPostDeleteReq true "请求参数"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post [delete]
// @Security Bearer
func (e SysPost) Delete(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("岗位删除失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "删除成功")
}
+284
View File
@@ -0,0 +1,284 @@
package apis
import (
"fmt"
"go-admin/common/global"
"net/http"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/v2/sdk"
"go-admin/app/admin/models"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/v2/response"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
)
type SysRole struct {
api.Api
}
// GetPage
// @Summary 角色列表数据
// @Description Get JSON
// @Tags 角色/Role
// @Param roleName query string false "roleName"
// @Param status query string false "status"
// @Param roleKey query string false "roleKey"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role [get]
// @Security Bearer
func (e SysRole) GetPage(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysRole, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 获取Role数据
// @Description 获取JSON
// @Tags 角色/Role
// @Param roleId path string false "roleId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role/{id} [get]
// @Security Bearer
func (e SysRole) Get(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, fmt.Sprintf(" %s ", err.Error()))
return
}
var object models.SysRole
err = s.Get(&req, &object)
if err != nil {
e.Error(http.StatusUnprocessableEntity, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 创建角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.SysRoleInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role [post]
// @Security Bearer
func (e SysRole) Insert(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置创建人
req.CreateBy = user.GetUserId(c)
if req.Status == "" {
req.Status = "2"
}
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
err = s.Insert(&req, cb)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "创建失败,"+err.Error())
return
}
_, err = global.LoadPolicy(c)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "创建失败,"+err.Error())
return
}
e.OK(req.GetId(), "创建成功")
}
// Update 修改用户角色
// @Summary 修改用户角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.SysRoleUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role/{id} [put]
// @Security Bearer
func (e SysRole) Update(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req, cb)
if err != nil {
e.Logger.Error(err)
return
}
_, err = global.LoadPolicy(c)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "更新失败,"+err.Error())
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除用户角色
// @Description 删除数据
// @Tags 角色/Role
// @Param data body dto.SysRoleDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role [delete]
// @Security Bearer
func (e SysRole) Delete(c *gin.Context) {
s := new(service.SysRole)
req := dto.SysRoleDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, fmt.Sprintf("删除角色 %v 失败,\r\n失败信息 %s", req.Ids, err.Error()))
return
}
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
err = s.Remove(&req, cb)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "")
return
}
e.OK(req.GetId(), fmt.Sprintf("删除角色角色 %v 状态成功!", req.GetId()))
}
// Update2Status 修改用户角色状态
// @Summary 修改用户角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.UpdateStatusReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role-status/{id} [put]
// @Security Bearer
func (e SysRole) Update2Status(c *gin.Context) {
s := service.SysRole{}
req := dto.UpdateStatusReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error()))
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.UpdateStatus(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error()))
return
}
e.OK(req.GetId(), fmt.Sprintf("更新角色 %v 状态成功!", req.GetId()))
}
// Update2DataScope 更新角色数据权限
// @Summary 更新角色数据权限
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.RoleDataScopeReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role-status/{id} [put]
// @Security Bearer
func (e SysRole) Update2DataScope(c *gin.Context) {
s := service.SysRole{}
req := dto.RoleDataScopeReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
data := &models.SysRole{
RoleId: req.RoleId,
DataScope: req.DataScope,
DeptIds: req.DeptIds,
}
data.UpdateBy = user.GetUserId(c)
err = s.UpdateDataScope(&req).Error
if err != nil {
e.Error(500, err, fmt.Sprintf("更新角色数据权限失败!错误详情:%s", err.Error()))
return
}
e.OK(nil, "操作成功")
}
+489
View File
@@ -0,0 +1,489 @@
package apis
import (
"errors"
"github.com/gin-gonic/gin/binding"
"go-admin/app/admin/models"
"golang.org/x/crypto/bcrypt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/v2/response"
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
"github.com/google/uuid"
"go-admin/app/admin/service"
"go-admin/app/admin/service/dto"
"go-admin/common/actions"
"go-admin/common/middleware"
)
type SysUser struct {
api.Api
}
// GetPage
// @Summary 列表用户信息数据
// @Description 获取JSON
// @Tags 用户
// @Param username query string false "username"
// @Success 200 {string} {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user [get]
// @Security Bearer
func (e SysUser) GetPage(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
//数据权限检查
p := actions.GetPermissionFromContext(c)
list := make([]models.SysUser, 0)
var count int64
err = s.GetPage(&req, p, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 获取用户
// @Description 获取JSON
// @Tags 用户
// @Param userId path int true "用户编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user/{userId} [get]
// @Security Bearer
func (e SysUser) Get(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserById{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysUser
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.Get(&req, p, &object)
if err != nil {
e.Error(http.StatusUnprocessableEntity, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 创建用户
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.SysUserInsertReq true "用户数据"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user [post]
// @Security Bearer
func (e SysUser) Insert(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置创建人
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改用户数据
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.SysUserUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user/{userId} [put]
// @Security Bearer
func (e SysUser) Update(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
callerId := user.GetUserId(c)
// This route is in CasbinExclude so the personal-center screen can edit
// the caller's own record without a policy grant (see settings.go). That
// exclusion covers the whole route, not just the caller's own record, and
// the request carries the target userId in the body - so without this
// check here, any authenticated caller could edit any other user, up to
// and including their roleId. When the target is someone else, ask Casbin
// directly for the permission AuthCheckRole skipped.
if req.UserId != callerId {
allowed, err := middleware.EnforceRoleFor(c, c.Request.URL.Path, c.Request.Method)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
if !allowed {
e.Error(http.StatusForbidden, errors.New("无权更新其他用户数据"), "对不起,您没有该接口访问权限,请联系管理员")
return
}
}
req.SetUpdateBy(callerId)
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.Update(&req, p, callerId)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除用户数据
// @Description 删除数据
// @Tags 用户
// @Param userId path int true "userId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user/{userId} [delete]
// @Security Bearer
func (e SysUser) Delete(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserById{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置编辑人
req.SetUpdateBy(user.GetUserId(c))
// 数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.Remove(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "删除成功")
}
// InsetAvatar
// @Summary 修改头像
// @Description 获取JSON
// @Tags 个人中心
// @Accept multipart/form-data
// @Param file formData file true "file"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/avatar [post]
// @Security Bearer
func (e SysUser) InsetAvatar(c *gin.Context) {
s := service.SysUser{}
req := dto.UpdateSysUserAvatarReq{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 数据权限检查
p := actions.GetPermissionFromContext(c)
form, _ := c.MultipartForm()
files := form.File["upload[]"]
guid := uuid.New().String()
filPath := "static/uploadfile/" + guid + ".jpg"
for _, file := range files {
e.Logger.Debugf("upload avatar file: %s", file.Filename)
// 上传文件至指定目录
err = c.SaveUploadedFile(file, filPath)
if err != nil {
e.Logger.Errorf("save file error, %s", err.Error())
e.Error(500, err, "")
return
}
}
req.UserId = p.UserId
req.Avatar = "/" + filPath
err = s.UpdateAvatar(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(filPath, "修改成功")
}
// UpdateStatus 修改用户状态
// @Summary 修改用户状态
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.UpdateSysUserStatusReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/status [put]
// @Security Bearer
func (e SysUser) UpdateStatus(c *gin.Context) {
s := service.SysUser{}
req := dto.UpdateSysUserStatusReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.UpdateStatus(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "更新成功")
}
// ResetPwd 重置用户密码
// @Summary 重置用户密码
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.ResetSysUserPwdReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/pwd/reset [put]
// @Security Bearer
func (e SysUser) ResetPwd(c *gin.Context) {
s := service.SysUser{}
req := dto.ResetSysUserPwdReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.ResetPwd(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "更新成功")
}
// UpdatePwd
// @Summary 修改密码
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.PassWord true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/pwd/set [put]
// @Security Bearer
func (e SysUser) UpdatePwd(c *gin.Context) {
s := service.SysUser{}
req := dto.PassWord{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 数据权限检查
p := actions.GetPermissionFromContext(c)
var hash []byte
if hash, err = bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost); err != nil {
req.NewPassword = string(hash)
}
err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p)
if err != nil {
e.Logger.Error(err)
e.Error(http.StatusForbidden, err, "密码修改失败")
return
}
e.OK(nil, "密码修改成功")
}
// GetProfile
// @Summary 获取个人中心用户
// @Description 获取JSON
// @Tags 个人中心
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/profile [get]
// @Security Bearer
func (e SysUser) GetProfile(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserById{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.Id = user.GetUserId(c)
sysUser := models.SysUser{}
roles := make([]models.SysRole, 0)
posts := make([]models.SysPost, 0)
err = s.GetProfile(&req, &sysUser, &roles, &posts)
if err != nil {
e.Logger.Errorf("get user profile error, %s", err.Error())
e.Error(500, err, "获取用户信息失败")
return
}
e.OK(gin.H{
"user": sysUser,
"roles": roles,
"posts": posts,
}, "查询成功")
}
// GetInfo
// @Summary 获取个人信息
// @Description 获取JSON
// @Tags 个人中心
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/getinfo [get]
// @Security Bearer
func (e SysUser) GetInfo(c *gin.Context) {
req := dto.SysUserById{}
s := service.SysUser{}
r := service.SysRole{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&r.Service).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var roles = make([]string, 1)
roles[0] = user.GetRoleName(c)
var permissions = make([]string, 1)
permissions[0] = "*:*:*"
var buttons = make([]string, 1)
buttons[0] = "*:*:*"
var mp = make(map[string]interface{})
mp["roles"] = roles
if user.GetRoleName(c) == "admin" || user.GetRoleName(c) == "系统管理员" {
mp["permissions"] = permissions
mp["buttons"] = buttons
} else {
list, _ := r.GetById(user.GetRoleId(c))
mp["permissions"] = list
mp["buttons"] = list
}
sysUser := models.SysUser{}
req.Id = user.GetUserId(c)
// Unscoped on purpose: the id is the caller's own, taken from the token.
// This used to go through Get with whatever GetPermissionFromContext
// returned - and this route installs no PermissionAction, so that was the
// zero value. An unset scope is not a recognised one, so once unknown
// scopes started failing closed rather than silently matching everything,
// every login on a deployment with enabledp: true ended here with a 401
// and the browser went straight back to the login page.
err = s.GetSelf(&req, &sysUser)
if err != nil {
e.Error(http.StatusUnauthorized, err, "登录失败")
return
}
mp["introduction"] = " am a super administrator"
mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
if sysUser.Avatar != "" {
mp["avatar"] = sysUser.Avatar
}
mp["userName"] = sysUser.Username
mp["userId"] = sysUser.UserId
mp["deptId"] = sysUser.DeptId
mp["name"] = sysUser.NickName
mp["code"] = 200
e.OK(mp, "")
}
+175
View File
@@ -0,0 +1,175 @@
package apis
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/glebarez/sqlite"
mycasbin "github.com/go-admin-team/go-admin-core/v2/casbin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"github.com/go-admin-team/go-admin-core/v2/logger"
"github.com/go-admin-team/go-admin-core/v2/sdk"
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
"gorm.io/gorm"
"go-admin/app/admin/models"
)
// PUT /api/v1/sys-user is in settings.go's CasbinExclude so the
// personal-center screen (go-admin-ui's userInfo.vue) can edit the caller's
// own record without holding a policy grant on this route. AuthCheckRole
// skips Enforce entirely for an excluded route, so this file's job is to pin
// what the handler itself now has to hold shut: the target userId comes from
// the request body, and nothing upstream of the handler ever checked it
// against the caller.
// setupPrivescDB wires an in-memory database and a Casbin enforcer with an
// empty policy - the state of a fresh install for any role but admin - under
// a tenant unique to the calling test, so mycasbin's process-wide enforcer
// cache can't hand one test's database to another.
func setupPrivescDB(t *testing.T) (*gorm.DB, string) {
t.Helper()
// Fatalf, not Skipf: this database is in-memory sqlite with no external
// dependency, so failing to open or migrate it means the environment is
// actually broken. Skipping here would let these two anti-privesc
// regression tests silently stop running while CI stays green - a
// standing assertion that never fires is worse than no assertion.
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{})
if err != nil {
t.Fatalf("sqlite unavailable: %v", err)
}
if err := db.AutoMigrate(&models.SysUser{}); err != nil {
t.Fatalf("automigrate: %v", err)
}
tenant := "sys-user-privesc-" + t.Name()
previousInterval := mycasbin.ReloadInterval
mycasbin.ReloadInterval = 0 // opt out of the background reload goroutine; the test never writes a policy
t.Cleanup(func() { mycasbin.ReloadInterval = previousInterval })
e := mycasbin.Setup(db, tenant)
previousEnforcer := sdk.Runtime.GetCasbinByTenant(tenant)
sdk.Runtime.SetCasbinByTenant(tenant, e)
t.Cleanup(func() { sdk.Runtime.SetCasbinByTenant(tenant, previousEnforcer) })
return db, tenant
}
// callUpdate drives SysUser.Update the way the router does for an
// authenticated, non-admin caller: JWT claims already decoded into the
// context (that is jwtauth's job, not this handler's) and a database - but
// without AuthCheckRole, since that middleware never runs Enforce for this
// route at all.
func callUpdate(t *testing.T, db *gorm.DB, tenant string, callerId int, body map[string]interface{}) *httptest.ResponseRecorder {
t.Helper()
gin.SetMode(gin.TestMode)
raw, err := json.Marshal(body)
if err != nil {
t.Fatalf("marshal request body: %v", err)
}
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodPut, "/api/v1/sys-user", bytes.NewReader(raw))
c.Request.Host = tenant
c.Request.Header.Set("Content-Type", "application/json")
c.Set("db", db)
c.Set(pkg.LoggerKey, logger.NewHelper(logger.DefaultLogger))
c.Set(jwt.JwtPayloadKey, jwt.MapClaims{
"identity": float64(callerId),
"rolekey": "ordinary-role", // holds no Casbin policy anywhere in this test
})
SysUser{}.Update(c)
return w
}
// TestUpdate_CannotEscalatePrivilegeThroughAnotherUsersRecord is the
// regression for H6. Before the fix, an ordinary authenticated user could PUT
// a body naming another user's id and change that user's roleId - the route
// being Casbin-excluded meant no permission check ever ran, and the data
// permission scope that would otherwise gate this is off by default.
func TestUpdate_CannotEscalatePrivilegeThroughAnotherUsersRecord(t *testing.T) {
db, tenant := setupPrivescDB(t)
victim := models.SysUser{Username: "bob", NickName: "Bob", RoleId: 2, DeptId: 1, Status: "1"}
if err := db.Create(&victim).Error; err != nil {
t.Fatal(err)
}
attacker := models.SysUser{Username: "alice", NickName: "Alice", RoleId: 2, DeptId: 1, Status: "1"}
if err := db.Create(&attacker).Error; err != nil {
t.Fatal(err)
}
const elevatedRoleId = 1 // a role the attacker does not hold and has no policy for
callUpdate(t, db, tenant, attacker.UserId, map[string]interface{}{
"userId": victim.UserId,
"username": victim.Username,
"nickName": "pwned",
"phone": "13800000000",
"email": "bob@example.com",
"roleId": elevatedRoleId,
"deptId": victim.DeptId,
"status": victim.Status,
})
var after models.SysUser
if err := db.First(&after, victim.UserId).Error; err != nil {
t.Fatal(err)
}
if after.RoleId == elevatedRoleId {
t.Fatalf("an attacker with no Casbin permission on this route escalated the victim's roleId to %d", after.RoleId)
}
if after.NickName == "pwned" {
t.Fatalf("an attacker with no Casbin permission on this route modified another user's record: %+v", after)
}
}
// TestUpdate_SelfEditCannotChangePrivilegedFields covers the case the
// CasbinExclude entry exists for: the personal-center screen has to keep
// working for the caller's own record. The fields that screen exposes
// (nickName/phone/email/sex) must still save, while roleId/deptId/status stay
// whatever the database already had even if the request carries something
// else - a compromised or hand-crafted client is the only way that request
// would ever differ from what the honest form sends.
func TestUpdate_SelfEditCannotChangePrivilegedFields(t *testing.T) {
db, tenant := setupPrivescDB(t)
self := models.SysUser{Username: "carol", NickName: "Carol", RoleId: 2, DeptId: 1, Status: "1"}
if err := db.Create(&self).Error; err != nil {
t.Fatal(err)
}
const elevatedRoleId = 1
callUpdate(t, db, tenant, self.UserId, map[string]interface{}{
"userId": self.UserId,
"username": self.Username,
"nickName": "Carol Updated",
"phone": "13900000000",
"email": "carol@example.com",
"roleId": elevatedRoleId, // tampered; must not take effect
"deptId": self.DeptId,
"status": self.Status,
})
var after models.SysUser
if err := db.First(&after, self.UserId).Error; err != nil {
t.Fatal(err)
}
if after.RoleId == elevatedRoleId {
t.Fatalf("a self-edit changed the caller's own roleId to %d", after.RoleId)
}
if after.NickName != "Carol Updated" {
t.Fatalf("the legitimate personal-center edit did not go through: %+v", after)
}
}
+16
View File
@@ -0,0 +1,16 @@
package models
type CasbinRule struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
V0 string `gorm:"size:512;uniqueIndex:unique_index"`
V1 string `gorm:"size:512;uniqueIndex:unique_index"`
V2 string `gorm:"size:512;uniqueIndex:unique_index"`
V3 string `gorm:"size:512;uniqueIndex:unique_index"`
V4 string `gorm:"size:512;uniqueIndex:unique_index"`
V5 string `gorm:"size:512;uniqueIndex:unique_index"`
}
func (CasbinRule) TableName() string {
return "sys_casbin_rule"
}
@@ -2,22 +2,24 @@ package models
import (
"fmt"
"go-admin/global"
"go-admin/common/global"
"gorm.io/gorm"
"io/ioutil"
"log"
"strings"
)
func InitDb() error {
func InitDb(db *gorm.DB) (err error) {
filePath := "config/db.sql"
err := ExecSql(filePath)
err = ExecSql(db, filePath)
if global.Driver == "postgres" {
filePath = "config/pg.sql"
err = ExecSql(filePath)
err = ExecSql(db, filePath)
}
return err
}
func ExecSql(filePath string) error {
func ExecSql(db *gorm.DB, filePath string) error {
sql, err := Ioutil(filePath)
if err != nil {
fmt.Println("数据库基础数据初始化脚本读取失败!原因:", err.Error())
@@ -29,9 +31,10 @@ func ExecSql(filePath string) error {
fmt.Println(sqlList[i])
continue
}
sql := strings.Replace(sqlList[i]+";", "\n", "", 0)
sql := strings.Replace(sqlList[i]+";", "\n", "", -1)
sql = strings.TrimSpace(sql)
if err = global.Eloquent.Exec(sql).Error; err != nil {
if err = db.Exec(sql).Error; err != nil {
log.Printf("error sql: %s", sql)
if !strings.Contains(err.Error(), "Query was empty") {
return err
}
+95
View File
@@ -0,0 +1,95 @@
package models
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"regexp"
"strings"
"github.com/bitly/go-simplejson"
"github.com/go-admin-team/go-admin-core/v2/sdk"
"github.com/go-admin-team/go-admin-core/v2/sdk/runtime"
"github.com/go-admin-team/go-admin-core/v2/storage"
"go-admin/common/models"
)
type SysApi struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
Handle string `json:"handle" gorm:"size:128;comment:handle"`
Title string `json:"title" gorm:"size:128;comment:标题"`
Path string `json:"path" gorm:"size:128;comment:地址"`
Action string `json:"action" gorm:"size:16;comment:请求类型"`
Type string `json:"type" gorm:"size:16;comment:接口类型"`
// AppCode identifies which application's seed.SeedMenus call wrote this
// row; empty for the host's own built-in APIs. Same NOT NULL DEFAULT ''
// reasoning as SysMenu.AppCode.
AppCode string `json:"appCode" gorm:"type:varchar(64);not null;default:'';index:idx_sys_api_app_code;comment:AppCode"`
models.ModelTime
models.ControlBy
}
func (*SysApi) TableName() string {
return "sys_api"
}
func (e *SysApi) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysApi) GetId() interface{} {
return e.Id
}
func SaveSysApi(message storage.Messager) (err error) {
var rb []byte
rb, err = json.Marshal(message.GetValues())
if err != nil {
err = fmt.Errorf("json Marshal error, %v", err.Error())
return err
}
var l runtime.Routers
err = json.Unmarshal(rb, &l)
if err != nil {
err = fmt.Errorf("json Unmarshal error, %s", err.Error())
return err
}
dbList := sdk.Runtime.GetAllDb()
for _, d := range dbList {
for _, v := range l.List {
if v.HttpMethod != "HEAD" ||
strings.Contains(v.RelativePath, "/swagger/") ||
strings.Contains(v.RelativePath, "/static/") ||
strings.Contains(v.RelativePath, "/form-generator/") ||
strings.Contains(v.RelativePath, "/sys/tables") {
// 根据接口方法注释里的@Summary填充接口名称,适用于代码生成器
// 可在此处增加配置路径前缀的if判断,只对代码生成的自建应用进行定向的接口名称填充
jsonFile, _ := ioutil.ReadFile("docs/swagger.json")
jsonData, _ := simplejson.NewFromReader(bytes.NewReader(jsonFile))
urlPath := v.RelativePath
idPatten := "(.*)/:(\\w+)" // 正则替换,把:id换成{id}
reg, _ := regexp.Compile(idPatten)
if reg.MatchString(urlPath) {
urlPath = reg.ReplaceAllString(v.RelativePath, "${1}/{${2}}") // 把:id换成{id}
}
apiTitle, _ := jsonData.Get("paths").Get(urlPath).Get(strings.ToLower(v.HttpMethod)).Get("summary").String()
err := d.Debug().Where(SysApi{Path: v.RelativePath, Action: v.HttpMethod}).
Attrs(SysApi{Handle: v.Handler, Title: apiTitle}).
FirstOrCreate(&SysApi{}).
//Update("handle", v.Handler).
Error
if err != nil {
err := fmt.Errorf("Models SaveSysApi error: %s \r\n ", err.Error())
return err
}
}
}
}
return nil
}
+30
View File
@@ -0,0 +1,30 @@
package models
import (
"go-admin/common/models"
)
type SysConfig struct {
models.Model
ConfigName string `json:"configName" gorm:"size:128;comment:ConfigName"` //
ConfigKey string `json:"configKey" gorm:"size:128;comment:ConfigKey"` //
ConfigValue string `json:"configValue" gorm:"size:255;comment:ConfigValue"` //
ConfigType string `json:"configType" gorm:"size:64;comment:ConfigType"`
IsFrontend string `json:"isFrontend" gorm:"size:64;comment:是否前台"` //
Remark string `json:"remark" gorm:"size:128;comment:Remark"` //
models.ControlBy
models.ModelTime
}
func (*SysConfig) TableName() string {
return "sys_config"
}
func (e *SysConfig) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysConfig) GetId() interface{} {
return e.Id
}
+33
View File
@@ -0,0 +1,33 @@
package models
import "go-admin/common/models"
type SysDept struct {
DeptId int `json:"deptId" gorm:"primaryKey;autoIncrement;"` //部门编码
ParentId int `json:"parentId" gorm:""` //上级部门
DeptPath string `json:"deptPath" gorm:"size:255;"` //
DeptName string `json:"deptName" gorm:"size:128;"` //部门名称
Sort int `json:"sort" gorm:"size:4;"` //排序
Leader string `json:"leader" gorm:"size:128;"` //负责人
Phone string `json:"phone" gorm:"size:11;"` //手机
Email string `json:"email" gorm:"size:64;"` //邮箱
Status int `json:"status" gorm:"size:4;"` //状态
models.ControlBy
models.ModelTime
DataScope string `json:"dataScope" gorm:"-"`
Params string `json:"params" gorm:"-"`
Children []SysDept `json:"children" gorm:"-"`
}
func (*SysDept) TableName() string {
return "sys_dept"
}
func (e *SysDept) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysDept) GetId() interface{} {
return e.DeptId
}
+34
View File
@@ -0,0 +1,34 @@
package models
import (
"go-admin/common/models"
)
type SysDictData struct {
DictCode int `json:"dictCode" gorm:"primaryKey;column:dict_code;autoIncrement;comment:主键编码"`
DictSort int `json:"dictSort" gorm:"size:20;comment:DictSort"`
DictLabel string `json:"dictLabel" gorm:"size:128;comment:DictLabel"`
DictValue string `json:"dictValue" gorm:"size:255;comment:DictValue"`
DictType string `json:"dictType" gorm:"size:64;comment:DictType"`
CssClass string `json:"cssClass" gorm:"size:128;comment:CssClass"`
ListClass string `json:"listClass" gorm:"size:128;comment:ListClass"`
IsDefault string `json:"isDefault" gorm:"size:8;comment:IsDefault"`
Status int `json:"status" gorm:"size:4;comment:Status"`
Default string `json:"default" gorm:"size:8;comment:Default"`
Remark string `json:"remark" gorm:"size:255;comment:Remark"`
models.ControlBy
models.ModelTime
}
func (*SysDictData) TableName() string {
return "sys_dict_data"
}
func (e *SysDictData) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysDictData) GetId() interface{} {
return e.DictCode
}
+28
View File
@@ -0,0 +1,28 @@
package models
import (
"go-admin/common/models"
)
type SysDictType struct {
ID int `json:"id" gorm:"primaryKey;column:dict_id;autoIncrement;comment:主键编码"`
DictName string `json:"dictName" gorm:"size:128;comment:DictName"`
DictType string `json:"dictType" gorm:"size:128;comment:DictType"`
Status int `json:"status" gorm:"size:4;comment:Status"`
Remark string `json:"remark" gorm:"size:255;comment:Remark"`
models.ControlBy
models.ModelTime
}
func (*SysDictType) TableName() string {
return "sys_dict_type"
}
func (e *SysDictType) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysDictType) GetId() interface{} {
return e.ID
}
+72
View File
@@ -0,0 +1,72 @@
package models
import (
"encoding/json"
"errors"
"time"
log "github.com/go-admin-team/go-admin-core/v2/logger"
"github.com/go-admin-team/go-admin-core/v2/sdk"
"github.com/go-admin-team/go-admin-core/v2/storage"
"go-admin/common/models"
)
type SysLoginLog struct {
models.Model
Username string `json:"username" gorm:"size:128;comment:用户名"`
Status string `json:"status" gorm:"size:4;comment:状态"`
Ipaddr string `json:"ipaddr" gorm:"size:255;comment:ip地址"`
LoginLocation string `json:"loginLocation" gorm:"size:255;comment:归属地"`
Browser string `json:"browser" gorm:"size:255;comment:浏览器"`
Os string `json:"os" gorm:"size:255;comment:系统"`
Platform string `json:"platform" gorm:"size:255;comment:固件"`
LoginTime time.Time `json:"loginTime" gorm:"comment:登录时间"`
Remark string `json:"remark" gorm:"size:255;comment:备注"`
Msg string `json:"msg" gorm:"size:255;comment:信息"`
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"`
models.ControlBy
}
func (*SysLoginLog) TableName() string {
return "sys_login_log"
}
func (e *SysLoginLog) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysLoginLog) GetId() interface{} {
return e.Id
}
// SaveLoginLog 从队列中获取登录日志
func SaveLoginLog(message storage.Messager) (err error) {
//准备db
db := sdk.Runtime.GetDbByTenant(message.GetPrefix())
if db == nil {
err = errors.New("db not exist")
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
return err
}
var rb []byte
rb, err = json.Marshal(message.GetValues())
if err != nil {
log.Errorf("json Marshal error, %s", err.Error())
return err
}
var l SysLoginLog
err = json.Unmarshal(rb, &l)
if err != nil {
log.Errorf("json Unmarshal error, %s", err.Error())
return err
}
err = db.Create(&l).Error
if err != nil {
log.Errorf("db create error, %s", err.Error())
return err
}
return nil
}
+56
View File
@@ -0,0 +1,56 @@
package models
import "go-admin/common/models"
type SysMenu struct {
MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"`
MenuName string `json:"menuName" gorm:"size:128;"`
Title string `json:"title" gorm:"size:128;"`
Icon string `json:"icon" gorm:"size:128;"`
Path string `json:"path" gorm:"size:128;"`
Paths string `json:"paths" gorm:"size:128;"`
MenuType string `json:"menuType" gorm:"size:1;"`
Action string `json:"action" gorm:"size:16;"`
Permission string `json:"permission" gorm:"size:255;"`
ParentId int `json:"parentId" gorm:"size:11;"`
NoCache bool `json:"noCache" gorm:"size:8;"`
Breadcrumb string `json:"breadcrumb" gorm:"size:255;"`
Component string `json:"component" gorm:"size:255;"`
Sort int `json:"sort" gorm:"size:4;"`
Visible string `json:"visible" gorm:"size:1;"`
IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"`
SysApi []SysApi `json:"sysApi" gorm:"many2many:sys_menu_api_rule"`
Apis []int `json:"apis" gorm:"-"`
DataScope string `json:"dataScope" gorm:"-"`
Params string `json:"params" gorm:"-"`
RoleId int `gorm:"-"`
Children []SysMenu `json:"children,omitempty" gorm:"-"`
IsSelect bool `json:"is_select" gorm:"-"`
// AppCode identifies which application's seed.SeedMenus call wrote this
// row; empty for the host's own built-in menus. NOT NULL DEFAULT '' for
// the same reason sys_migration.app_code is (see contract/models.Migration):
// AutoMigrate adding this column to an existing table leaves every
// pre-existing row reading back as "" rather than NULL.
AppCode string `json:"appCode" gorm:"type:varchar(64);not null;default:'';index:idx_sys_menu_app_code;comment:AppCode"`
models.ControlBy
models.ModelTime
}
type SysMenuSlice []SysMenu
func (x SysMenuSlice) Len() int { return len(x) }
func (x SysMenuSlice) Less(i, j int) bool { return x[i].Sort < x[j].Sort }
func (x SysMenuSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (*SysMenu) TableName() string {
return "sys_menu"
}
func (e *SysMenu) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysMenu) GetId() interface{} {
return e.MenuId
}
+88
View File
@@ -0,0 +1,88 @@
package models
import (
"encoding/json"
"errors"
"time"
log "github.com/go-admin-team/go-admin-core/v2/logger"
"github.com/go-admin-team/go-admin-core/v2/sdk"
"github.com/go-admin-team/go-admin-core/v2/storage"
"go-admin/common/models"
)
type SysOperaLog struct {
models.Model
Title string `json:"title" gorm:"size:255;comment:操作模块"`
BusinessType string `json:"businessType" gorm:"size:128;comment:操作类型"`
BusinessTypes string `json:"businessTypes" gorm:"size:128;comment:BusinessTypes"`
Method string `json:"method" gorm:"size:128;comment:函数"`
RequestMethod string `json:"requestMethod" gorm:"size:128;comment:请求方式 GET POST PUT DELETE"`
OperatorType string `json:"operatorType" gorm:"size:128;comment:操作类型"`
OperName string `json:"operName" gorm:"size:128;comment:操作者"`
DeptName string `json:"deptName" gorm:"size:128;comment:部门名称"`
OperUrl string `json:"operUrl" gorm:"size:255;comment:访问地址"`
OperIp string `json:"operIp" gorm:"size:128;comment:客户端ip"`
OperLocation string `json:"operLocation" gorm:"size:128;comment:访问位置"`
OperParam string `json:"operParam" gorm:"text;comment:请求参数"`
Status string `json:"status" gorm:"size:4;comment:操作状态 1:正常 2:关闭"`
OperTime time.Time `json:"operTime" gorm:"comment:操作时间"`
JsonResult string `json:"jsonResult" gorm:"size:255;comment:返回数据"`
Remark string `json:"remark" gorm:"size:255;comment:备注"`
LatencyTime string `json:"latencyTime" gorm:"size:128;comment:耗时"`
UserAgent string `json:"userAgent" gorm:"size:255;comment:ua"`
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"`
models.ControlBy
}
func (*SysOperaLog) TableName() string {
return "sys_opera_log"
}
func (e *SysOperaLog) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysOperaLog) GetId() interface{} {
return e.Id
}
// SaveOperaLog 从队列中获取操作日志
func SaveOperaLog(message storage.Messager) (err error) {
//准备db
db := sdk.Runtime.GetDbByTenant(message.GetPrefix())
if db == nil {
err = errors.New("db not exist")
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
// Log writing to the database ignores error
return nil
}
var rb []byte
rb, err = json.Marshal(message.GetValues())
if err != nil {
log.Errorf("json Marshal error, %s", err.Error())
// Log writing to the database ignores error
return nil
}
var l SysOperaLog
err = json.Unmarshal(rb, &l)
if err != nil {
log.Errorf("json Unmarshal error, %s", err.Error())
// Log writing to the database ignores error
return nil
}
// 超出100个字符返回值截断
if len(l.JsonResult) > 100 {
l.JsonResult = l.JsonResult[:100]
}
err = db.Create(&l).Error
if err != nil {
log.Errorf("db create error, %s", err.Error())
// Log writing to the database ignores error
return nil
}
return nil
}
+30
View File
@@ -0,0 +1,30 @@
package models
import "go-admin/common/models"
type SysPost struct {
PostId int `gorm:"primaryKey;autoIncrement" json:"postId"` //岗位编号
PostName string `gorm:"size:128;" json:"postName"` //岗位名称
PostCode string `gorm:"size:128;" json:"postCode"` //岗位代码
Sort int `gorm:"size:4;" json:"sort"` //岗位排序
Status int `gorm:"size:4;" json:"status"` //状态
Remark string `gorm:"size:255;" json:"remark"` //描述
models.ControlBy
models.ModelTime
DataScope string `gorm:"-" json:"dataScope"`
Params string `gorm:"-" json:"params"`
}
func (*SysPost) TableName() string {
return "sys_post"
}
func (e *SysPost) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysPost) GetId() interface{} {
return e.PostId
}
+35
View File
@@ -0,0 +1,35 @@
package models
import "go-admin/common/models"
type SysRole struct {
RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码
RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称
Status string `json:"status" gorm:"size:4;"` // 状态 1禁用 2正常
RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码
RoleSort int `json:"roleSort" gorm:""` //角色排序
Flag string `json:"flag" gorm:"size:128;"` //
Remark string `json:"remark" gorm:"size:255;"` //备注
Admin bool `json:"admin" gorm:"size:4;"`
DataScope string `json:"dataScope" gorm:"size:128;"`
Params string `json:"params" gorm:"-"`
MenuIds []int `json:"menuIds" gorm:"-"`
DeptIds []int `json:"deptIds" gorm:"-"`
SysDept []SysDept `json:"sysDept" gorm:"many2many:sys_role_dept;foreignKey:RoleId;joinForeignKey:role_id;references:DeptId;joinReferences:dept_id;"`
SysMenu *[]SysMenu `json:"sysMenu" gorm:"many2many:sys_role_menu;foreignKey:RoleId;joinForeignKey:role_id;references:MenuId;joinReferences:menu_id;"`
models.ControlBy
models.ModelTime
}
func (*SysRole) TableName() string {
return "sys_role"
}
func (e *SysRole) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysRole) GetId() interface{} {
return e.RoleId
}
+89
View File
@@ -0,0 +1,89 @@
package models
import (
"go-admin/common/models"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
type SysUser struct {
UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"`
Username string `json:"username" gorm:"size:64;comment:用户名"`
Password string `json:"-" gorm:"size:128;comment:密码"`
NickName string `json:"nickName" gorm:"size:128;comment:昵称"`
Phone string `json:"phone" gorm:"size:11;comment:手机号"`
RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"`
Salt string `json:"-" gorm:"size:255;comment:加盐"`
Avatar string `json:"avatar" gorm:"size:255;comment:头像"`
Sex string `json:"sex" gorm:"size:255;comment:性别"`
Email string `json:"email" gorm:"size:128;comment:邮箱"`
DeptId int `json:"deptId" gorm:"size:20;comment:部门"`
PostId int `json:"postId" gorm:"size:20;comment:岗位"`
Remark string `json:"remark" gorm:"size:255;comment:备注"`
Status string `json:"status" gorm:"size:4;comment:状态"`
DeptIds []int `json:"deptIds" gorm:"-"`
PostIds []int `json:"postIds" gorm:"-"`
RoleIds []int `json:"roleIds" gorm:"-"`
Dept *SysDept `json:"dept"`
models.ControlBy
models.ModelTime
}
func (*SysUser) TableName() string {
return "sys_user"
}
func (e *SysUser) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysUser) GetId() interface{} {
return e.UserId
}
// Encrypt hashes Password, unless it already holds a hash.
//
// The hooks below run on whatever is in the struct, and a user read from the
// database carries the stored hash in that field. Hashing it again produces a
// hash of a hash, and the password that user knows no longer matches anything:
// they cannot log in, and nothing reports an error. The only thing preventing
// that today is an Omit("password") on the one update that loads a user first,
// which makes every other write to this model one line away from destroying
// credentials.
//
// bcrypt.Cost parses a hash and fails on anything else, so it distinguishes
// the two cases without the call site having to say which it is. The cost is
// that a password which is itself a well-formed bcrypt hash would be stored
// unchanged - a 60-character string beginning "$2a$", not something a person
// types, and it grants whoever set it no access they did not already have.
func (e *SysUser) Encrypt() error {
if e.Password == "" {
return nil
}
if _, err := bcrypt.Cost([]byte(e.Password)); err == nil {
return nil
}
hash, err := bcrypt.GenerateFromPassword([]byte(e.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
e.Password = string(hash)
return nil
}
func (e *SysUser) BeforeCreate(_ *gorm.DB) error {
return e.Encrypt()
}
func (e *SysUser) BeforeUpdate(_ *gorm.DB) error {
return e.Encrypt()
}
func (e *SysUser) AfterFind(_ *gorm.DB) error {
e.DeptIds = []int{e.DeptId}
e.PostIds = []int{e.PostId}
e.RoleIds = []int{e.RoleId}
return nil
}
+106
View File
@@ -0,0 +1,106 @@
package models
import (
"testing"
"golang.org/x/crypto/bcrypt"
)
const knownPassword = "correct-horse-battery-staple"
// A user loaded from the database carries the stored hash in Password, and the
// hooks run on whatever is in the struct. Hashing it a second time produces a
// hash of a hash: the password the user knows stops matching, they cannot log
// in, and nothing reports an error.
//
// Only an Omit("password") on one call site stood between this and every write
// to the model. This is the test that removes the need for it.
func TestEncryptLeavesAnAlreadyHashedPasswordAlone(t *testing.T) {
fresh := SysUser{Password: knownPassword}
if err := fresh.Encrypt(); err != nil {
t.Fatalf("Encrypt: %v", err)
}
stored := fresh.Password
if err := bcrypt.CompareHashAndPassword([]byte(stored), []byte(knownPassword)); err != nil {
t.Fatalf("setup failed: the password was not hashed: %v", err)
}
// What a query puts in the struct, and what an update then hands the hook.
loaded := SysUser{Password: stored}
if err := loaded.Encrypt(); err != nil {
t.Fatalf("Encrypt on a loaded user: %v", err)
}
if loaded.Password != stored {
t.Error("Encrypt re-hashed a stored hash; the user can no longer log in")
}
if err := bcrypt.CompareHashAndPassword([]byte(loaded.Password), []byte(knownPassword)); err != nil {
t.Errorf("the user can no longer log in with their password: %v", err)
}
}
// The other half: a password that is not a hash still gets hashed, on create
// and on update alike.
func TestEncryptHashesAPlaintextPassword(t *testing.T) {
for _, c := range []struct {
name string
hook func(*SysUser) error
}{
{"BeforeCreate", func(u *SysUser) error { return u.BeforeCreate(nil) }},
{"BeforeUpdate", func(u *SysUser) error { return u.BeforeUpdate(nil) }},
} {
t.Run(c.name, func(t *testing.T) {
u := SysUser{Password: knownPassword}
if err := c.hook(&u); err != nil {
t.Fatal(err)
}
if u.Password == knownPassword {
t.Fatal("the password was stored as it was typed")
}
if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(knownPassword)); err != nil {
t.Errorf("the stored value does not verify the password: %v", err)
}
})
}
}
// An empty Password means "not being set", and must not become a hash of "".
func TestEncryptIgnoresAnEmptyPassword(t *testing.T) {
u := SysUser{}
if err := u.Encrypt(); err != nil {
t.Fatal(err)
}
if u.Password != "" {
t.Errorf("an unset password became %q", u.Password)
}
}
// Encrypt runs on every update of this model, including the ones that change
// something else entirely. What it costs when there is nothing to do is the
// difference between a profile update and a bcrypt round; the correctness test
// above is what catches a regression, this reports the size of it.
func BenchmarkEncrypt(b *testing.B) {
fresh := SysUser{Password: knownPassword}
if err := fresh.Encrypt(); err != nil {
b.Fatal(err)
}
b.Run("already hashed", func(b *testing.B) {
u := SysUser{Password: fresh.Password}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if err := u.Encrypt(); err != nil {
b.Fatal(err)
}
}
})
b.Run("plaintext", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
u := SysUser{Password: knownPassword}
if err := u.Encrypt(); err != nil {
b.Fatal(err)
}
}
})
}
+38
View File
@@ -0,0 +1,38 @@
package router
import (
"os"
"github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/v2/logger"
"github.com/go-admin-team/go-admin-core/v2/sdk"
common "go-admin/common/middleware"
)
// InitRouter 路由初始化,不要怀疑,这里用到了
func InitRouter() {
var r *gin.Engine
h := sdk.Runtime.GetEngine()
if h == nil {
log.Fatal("not found engine...")
os.Exit(-1)
}
switch h.(type) {
case *gin.Engine:
r = h.(*gin.Engine)
default:
log.Fatal("not support other engine")
os.Exit(-1)
}
// the jwt middleware: shared instance InitMiddleware built at startup,
// not one built here per module (see common/middleware.GetAuthMiddleware).
authMiddleware := common.GetAuthMiddleware()
// 注册系统路由
InitSysRouter(r, authMiddleware)
// 注册业务路由
// TODO: 这里可存放业务路由,里边并无实际路由只有演示代码
InitExamplesRouter(r, authMiddleware)
}
+13 -11
View File
@@ -3,11 +3,15 @@ package router
import (
"github.com/gin-gonic/gin"
_ "github.com/gin-gonic/gin"
"go-admin/pkg/jwtauth"
jwt "go-admin/pkg/jwtauth"
"github.com/go-admin-team/go-admin-core/v2/jwtauth"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
)
var (
routerNoCheckRole = make([]func(*gin.RouterGroup), 0)
routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0)
)
// 路由示例
func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine {
// 无需认证的路由
@@ -22,18 +26,16 @@ func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gi
func examplesNoCheckRoleRouter(r *gin.Engine) {
// 可根据业务需求来设置接口版本
v1 := r.Group("/api/v1")
// 空接口防止v1定义无使用报错
v1.GET("/nilcheckrole", nil)
// {{无需认证路由自动补充在此处请勿删除}}
for _, f := range routerNoCheckRole {
f(v1)
}
}
// 需要认证的路由示例
func examplesCheckRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) {
// 可根据业务需求来设置接口版本
v1 := r.Group("/api/v1")
// 空接口防止v1定义无使用报错
v1.GET("/checkrole", nil)
// {{认证路由自动补充在此处请勿删除}}
for _, f := range routerCheckRole {
f(v1, authMiddleware)
}
}
+28
View File
@@ -0,0 +1,28 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/actions"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysApiRouter)
}
// registerSysApiRouter
func registerSysApiRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysApi{}
// PermissionAction is not optional here: all three handlers below read the
// data permission out of the context, and without it they read the zero
// value - an unset scope, which Permission now fails closed on.
r := v1.Group("/sys-api").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.PUT("/:id", api.Update)
}
}
+43
View File
@@ -0,0 +1,43 @@
package router
import (
"go-admin/app/admin/apis"
"go-admin/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysConfigRouter)
}
// 需认证的路由代码
func registerSysConfigRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysConfig{}
r := v1.Group("/config").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("/configKey").Use(authMiddleware.MiddlewareFunc())
{
r1.GET("/:configKey", api.GetSysConfigByKEYForService)
}
r2 := v1.Group("/app-config")
{
r2.GET("", api.Get2SysApp)
}
r3 := v1.Group("/set-config").Use(authMiddleware.MiddlewareFunc())
{
r3.PUT("", api.Update2Set)
r3.GET("", api.Get2Set)
}
}
+32
View File
@@ -0,0 +1,32 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysDeptRouter)
}
// 需认证的路由代码
func registerSysDeptRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysDept{}
r := v1.Group("/dept").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc())
{
r1.GET("/deptTree", api.Get2Tree)
}
}
+37
View File
@@ -0,0 +1,37 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerDictRouter)
}
func registerDictRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
dictApi := apis.SysDictType{}
dataApi := apis.SysDictData{}
dicts := v1.Group("/dict").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
dicts.GET("/data", dataApi.GetPage)
dicts.GET("/data/:dictCode", dataApi.Get)
dicts.POST("/data", dataApi.Insert)
dicts.PUT("/data/:dictCode", dataApi.Update)
dicts.DELETE("/data", dataApi.Delete)
dicts.GET("/type-option-select", dictApi.GetAll)
dicts.GET("/type", dictApi.GetPage)
dicts.GET("/type/:id", dictApi.Get)
dicts.POST("/type", dictApi.Insert)
dicts.PUT("/type/:id", dictApi.Update)
dicts.DELETE("/type", dictApi.Delete)
}
opSelect := v1.Group("/dict-data").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
opSelect.GET("/option-select", dataApi.GetAll)
}
}
+24
View File
@@ -0,0 +1,24 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysLoginLogRouter)
}
// 需认证的路由代码
func registerSysLoginLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysLoginLog{}
r := v1.Group("/sys-login-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.DELETE("", api.Delete)
}
}
+33
View File
@@ -0,0 +1,33 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysMenuRouter)
}
// 需认证的路由代码
func registerSysMenuRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysMenu{}
r := v1.Group("/menu").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc())
{
r1.GET("/menurole", api.GetMenuRole)
//r1.GET("/menuids", api.GetMenuIDS)
}
}
+23
View File
@@ -0,0 +1,23 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysOperaLogRouter)
}
// 需认证的路由代码
func registerSysOperaLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysOperaLog{}
r := v1.Group("/sys-opera-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.DELETE("", api.Delete)
}
}
+25
View File
@@ -0,0 +1,25 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSyPostRouter)
}
// 需认证的路由代码
func registerSyPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysPost{}
r := v1.Group("/post").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
}
+31
View File
@@ -0,0 +1,31 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysRoleRouter)
}
// 需认证的路由代码
func registerSysRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysRole{}
r := v1.Group("/role").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc())
{
r1.PUT("/role-status", api.Update2Status)
r1.PUT("/roledatascope", api.Update2DataScope)
}
}
+95
View File
@@ -0,0 +1,95 @@
package router
import (
"go-admin/app/admin/apis"
"mime"
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg/ws"
ginSwagger "github.com/swaggo/gin-swagger"
swaggerfiles "github.com/swaggo/files"
"go-admin/common/middleware"
"go-admin/common/middleware/handler"
_ "go-admin/docs/admin"
)
func InitSysRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterGroup {
g := r.Group("")
sysBaseRouter(g)
// 静态文件
sysStaticFileRouter(g)
// swagger;注意:生产环境可以注释掉
if config.ApplicationConfig.Mode != "prod" {
sysSwaggerRouter(g)
}
// 需要认证
sysCheckRoleRouterInit(g, authMiddleware)
return g
}
func sysBaseRouter(r *gin.RouterGroup) {
go ws.WebsocketManager.Start()
go ws.WebsocketManager.SendService()
go ws.WebsocketManager.SendAllService()
if config.ApplicationConfig.Mode != "prod" {
r.GET("/", apis.GoAdmin)
}
r.GET("/info", handler.Ping)
}
func sysStaticFileRouter(r *gin.RouterGroup) {
err := mime.AddExtensionType(".js", "application/javascript")
if err != nil {
return
}
r.Static("/static", "./static")
if config.ApplicationConfig.Mode != "prod" {
r.Static("/form-generator", "./static/form-generator")
}
}
func sysSwaggerRouter(r *gin.RouterGroup) {
r.GET("/swagger/admin/*any", ginSwagger.WrapHandler(swaggerfiles.NewHandler(), ginSwagger.InstanceName("admin")))
}
func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
wss := r.Group("").Use(authMiddleware.MiddlewareFunc())
{
wss.GET("/ws/:id/:channel", ws.WebsocketManager.WsClient)
wss.GET("/wslogout/:id/:channel", ws.WebsocketManager.UnWsClient)
}
v1 := r.Group("/api/v1")
{
v1.POST("/login", authMiddleware.LoginHandler)
// GET /api/v1/refresh_token 已移除,原因见 issue #820:
// 该接口用业务 token 即可换取新 token,而续期上限 MaxRefresh 依据的
// orig_iat 在每次续期时被一并重置,上限永远无法到达 —— token 一旦泄
// 露即等同于永久访问权。它此前还位于 CasbinExclude 中,任何角色的已
// 登录用户都能调用,不受权限约束。
//
// 官方前端从未调用该接口(store 中的 refreshToken action 无人 dispatch),
// 移除不影响正常使用。若确需无感续期,应在 go-admin-core 中区分
// access token 与 refresh token 后重新实现,而非沿用此路由。
}
registerBaseRouter(v1, authMiddleware)
}
func registerBaseRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysMenu{}
api2 := apis.SysDept{}
v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
v1auth.GET("/roleMenuTreeselect/:roleId", api.GetMenuTreeSelect)
//v1.GET("/menuTreeselect", api.GetMenuTreeSelect)
v1auth.GET("/roleDeptTreeselect/:roleId", api2.GetDeptTreeRoleSelect)
v1auth.POST("/logout", handler.LogOut)
}
}
+39
View File
@@ -0,0 +1,39 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
"go-admin/app/admin/apis"
"go-admin/common/actions"
"go-admin/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysUserRouter)
}
// 需认证的路由代码
func registerSysUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysUser{}
r := v1.Group("/sys-user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("", api.Update)
r.DELETE("", api.Delete)
}
user := v1.Group("/user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
{
user.GET("/profile", api.GetProfile)
user.POST("/avatar", api.InsetAvatar)
user.PUT("/pwd/set", api.UpdatePwd)
user.PUT("/pwd/reset", api.ResetPwd)
user.PUT("/status", api.UpdateStatus)
}
v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc())
{
v1auth.GET("/getinfo", api.GetInfo)
}
}
+95
View File
@@ -0,0 +1,95 @@
package dto
import (
"go-admin/app/admin/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
// SysApiGetPageReq 功能列表请求参数
type SysApiGetPageReq struct {
dto.Pagination `search:"-"`
Title string `form:"title" search:"type:contains;column:title;table:sys_api" comment:"标题"`
Path string `form:"path" search:"type:contains;column:path;table:sys_api" comment:"地址"`
Action string `form:"action" search:"type:exact;column:action;table:sys_api" comment:"请求方式"`
ParentId string `form:"parentId" search:"type:exact;column:parent_id;table:sys_api" comment:"按钮id"`
Type string `form:"type" search:"-" comment:"类型"`
SysApiOrder
}
type SysApiOrder struct {
TitleOrder string `search:"type:order;column:title;table:sys_api" form:"titleOrder"`
PathOrder string `search:"type:order;column:path;table:sys_api" form:"pathOrder"`
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_api" form:"createdAtOrder"`
}
func (m *SysApiGetPageReq) GetNeedSearch() interface{} {
return *m
}
// SysApiInsertReq 功能创建请求参数
type SysApiInsertReq struct {
Id int `json:"-" comment:"编码"` // 编码
Handle string `json:"handle" comment:"handle"`
Title string `json:"title" comment:"标题"`
Path string `json:"path" comment:"地址"`
Type string `json:"type" comment:""`
Action string `json:"action" comment:"类型"`
common.ControlBy
}
func (s *SysApiInsertReq) Generate(model *models.SysApi) {
model.Handle = s.Handle
model.Title = s.Title
model.Path = s.Path
model.Type = s.Type
model.Action = s.Action
}
func (s *SysApiInsertReq) GetId() interface{} {
return s.Id
}
// SysApiUpdateReq 功能更新请求参数
type SysApiUpdateReq struct {
Id int `uri:"id" comment:"编码"` // 编码
Handle string `json:"handle" comment:"handle"`
Title string `json:"title" comment:"标题"`
Path string `json:"path" comment:"地址"`
Type string `json:"type" comment:""`
Action string `json:"action" comment:"类型"`
common.ControlBy
}
func (s *SysApiUpdateReq) Generate(model *models.SysApi) {
if s.Id != 0 {
model.Id = s.Id
}
model.Handle = s.Handle
model.Title = s.Title
model.Path = s.Path
model.Type = s.Type
model.Action = s.Action
}
func (s *SysApiUpdateReq) GetId() interface{} {
return s.Id
}
// SysApiGetReq 功能获取请求参数
type SysApiGetReq struct {
Id int `uri:"id"`
}
func (s *SysApiGetReq) GetId() interface{} {
return s.Id
}
// SysApiDeleteReq 功能删除请求参数
type SysApiDeleteReq struct {
Ids []int `json:"ids"`
}
func (s *SysApiDeleteReq) GetId() interface{} {
return s.Ids
}
+112
View File
@@ -0,0 +1,112 @@
package dto
import (
"go-admin/app/admin/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
// SysConfigGetPageReq 列表或者搜索使用结构体
type SysConfigGetPageReq struct {
dto.Pagination `search:"-"`
ConfigName string `form:"configName" search:"type:contains;column:config_name;table:sys_config"`
ConfigKey string `form:"configKey" search:"type:contains;column:config_key;table:sys_config"`
ConfigType string `form:"configType" search:"type:exact;column:config_type;table:sys_config"`
IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
SysConfigOrder
}
type SysConfigOrder struct {
IdOrder string `search:"type:order;column:id;table:sys_config" form:"idOrder"`
ConfigNameOrder string `search:"type:order;column:config_name;table:sys_config" form:"configNameOrder"`
ConfigKeyOrder string `search:"type:order;column:config_key;table:sys_config" form:"configKeyOrder"`
ConfigTypeOrder string `search:"type:order;column:config_type;table:sys_config" form:"configTypeOrder"`
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_config" form:"createdAtOrder"`
}
func (m *SysConfigGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysConfigGetToSysAppReq struct {
IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
}
func (m *SysConfigGetToSysAppReq) GetNeedSearch() interface{} {
return *m
}
// SysConfigControl 增、改使用的结构体
type SysConfigControl struct {
Id int `uri:"Id" comment:"编码"` // 编码
ConfigName string `json:"configName" comment:""`
ConfigKey string `uri:"configKey" json:"configKey" comment:""`
ConfigValue string `json:"configValue" comment:""`
ConfigType string `json:"configType" comment:""`
IsFrontend string `json:"isFrontend"`
Remark string `json:"remark" comment:""`
common.ControlBy
}
// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型
func (s *SysConfigControl) Generate(model *models.SysConfig) {
if s.Id == 0 {
model.Model = common.Model{Id: s.Id}
}
model.ConfigName = s.ConfigName
model.ConfigKey = s.ConfigKey
model.ConfigValue = s.ConfigValue
model.ConfigType = s.ConfigType
model.IsFrontend = s.IsFrontend
model.Remark = s.Remark
}
// GetId 获取数据对应的ID
func (s *SysConfigControl) GetId() interface{} {
return s.Id
}
// GetSetSysConfigReq 增、改使用的结构体
type GetSetSysConfigReq struct {
ConfigKey string `json:"configKey" comment:""`
ConfigValue string `json:"configValue" comment:""`
}
// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型
func (s *GetSetSysConfigReq) Generate(model *models.SysConfig) {
model.ConfigValue = s.ConfigValue
}
type UpdateSetSysConfigReq map[string]string
// SysConfigByKeyReq 根据Key获取配置
type SysConfigByKeyReq struct {
ConfigKey string `uri:"configKey" search:"type:contains;column:config_key;table:sys_config"`
}
func (m *SysConfigByKeyReq) GetNeedSearch() interface{} {
return *m
}
type GetSysConfigByKEYForServiceResp struct {
ConfigKey string `json:"configKey" comment:""`
ConfigValue string `json:"configValue" comment:""`
}
type SysConfigGetReq struct {
Id int `uri:"id"`
}
func (s *SysConfigGetReq) GetId() interface{} {
return s.Id
}
type SysConfigDeleteReq struct {
Ids []int `json:"ids"`
common.ControlBy
}
func (s *SysConfigDeleteReq) GetId() interface{} {
return s.Ids
}
+110
View File
@@ -0,0 +1,110 @@
package dto
import (
"go-admin/app/admin/models"
common "go-admin/common/models"
)
// SysDeptGetPageReq 列表或者搜索使用结构体
type SysDeptGetPageReq struct {
DeptId int `form:"deptId" search:"type:exact;column:dept_id;table:sys_dept" comment:"id"` //id
ParentId int `form:"parentId" search:"type:exact;column:parent_id;table:sys_dept" comment:"上级部门"` //上级部门
DeptPath string `form:"deptPath" search:"type:exact;column:dept_path;table:sys_dept" comment:""` //路径
DeptName string `form:"deptName" search:"type:exact;column:dept_name;table:sys_dept" comment:"部门名称"` //部门名称
Sort int `form:"sort" search:"type:exact;column:sort;table:sys_dept" comment:"排序"` //排序
Leader string `form:"leader" search:"type:exact;column:leader;table:sys_dept" comment:"负责人"` //负责人
Phone string `form:"phone" search:"type:exact;column:phone;table:sys_dept" comment:"手机"` //手机
Email string `form:"email" search:"type:exact;column:email;table:sys_dept" comment:"邮箱"` //邮箱
Status string `form:"status" search:"type:exact;column:status;table:sys_dept" comment:"状态"` //状态
}
func (m *SysDeptGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysDeptInsertReq struct {
DeptId int `uri:"id" comment:"编码"` // 编码
ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门
DeptPath string `json:"deptPath" comment:""` //路径
DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称
Sort int `json:"sort" comment:"排序" vd:"?"` //排序
Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人
Phone string `json:"phone" comment:"手机" vd:"?"` //手机
Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱
Status int `json:"status" comment:"状态" vd:"$>0"` //状态
common.ControlBy
}
func (s *SysDeptInsertReq) Generate(model *models.SysDept) {
if s.DeptId != 0 {
model.DeptId = s.DeptId
}
model.DeptName = s.DeptName
model.ParentId = s.ParentId
model.DeptPath = s.DeptPath
model.Sort = s.Sort
model.Leader = s.Leader
model.Phone = s.Phone
model.Email = s.Email
model.Status = s.Status
}
// GetId 获取数据对应的ID
func (s *SysDeptInsertReq) GetId() interface{} {
return s.DeptId
}
type SysDeptUpdateReq struct {
DeptId int `uri:"id" comment:"编码"` // 编码
ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门
DeptPath string `json:"deptPath" comment:""` //路径
DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称
Sort int `json:"sort" comment:"排序" vd:"?"` //排序
Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人
Phone string `json:"phone" comment:"手机" vd:"?"` //手机
Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱
Status int `json:"status" comment:"状态" vd:"$>0"` //状态
common.ControlBy
}
// Generate 结构体数据转化 从 SysDeptControl 至 SysDept 对应的模型
func (s *SysDeptUpdateReq) Generate(model *models.SysDept) {
if s.DeptId != 0 {
model.DeptId = s.DeptId
}
model.DeptName = s.DeptName
model.ParentId = s.ParentId
model.DeptPath = s.DeptPath
model.Sort = s.Sort
model.Leader = s.Leader
model.Phone = s.Phone
model.Email = s.Email
model.Status = s.Status
}
// GetId 获取数据对应的ID
func (s *SysDeptUpdateReq) GetId() interface{} {
return s.DeptId
}
type SysDeptGetReq struct {
Id int `uri:"id"`
}
func (s *SysDeptGetReq) GetId() interface{} {
return s.Id
}
type SysDeptDeleteReq struct {
Ids []int `json:"ids"`
}
func (s *SysDeptDeleteReq) GetId() interface{} {
return s.Ids
}
type DeptLabel struct {
Id int `gorm:"-" json:"id"`
Label string `gorm:"-" json:"label"`
Children []DeptLabel `gorm:"-" json:"children"`
}
+108
View File
@@ -0,0 +1,108 @@
package dto
import (
"go-admin/app/admin/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
type SysDictDataGetPageReq struct {
dto.Pagination `search:"-"`
Id int `form:"id" search:"type:exact;column:dict_code;table:sys_dict_data" comment:""`
DictLabel string `form:"dictLabel" search:"type:contains;column:dict_label;table:sys_dict_data" comment:""`
DictValue string `form:"dictValue" search:"type:contains;column:dict_value;table:sys_dict_data" comment:""`
DictType string `form:"dictType" search:"type:contains;column:dict_type;table:sys_dict_data" comment:""`
Status string `form:"status" search:"type:exact;column:status;table:sys_dict_data" comment:""`
}
func (m *SysDictDataGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysDictDataGetAllResp struct {
DictLabel string `json:"label"`
DictValue string `json:"value"`
}
type SysDictDataInsertReq struct {
Id int `json:"-" comment:""`
DictSort int `json:"dictSort" comment:""`
DictLabel string `json:"dictLabel" comment:""`
DictValue string `json:"dictValue" comment:""`
DictType string `json:"dictType" comment:""`
CssClass string `json:"cssClass" comment:""`
ListClass string `json:"listClass" comment:""`
IsDefault string `json:"isDefault" comment:""`
Status int `json:"status" comment:""`
Default string `json:"default" comment:""`
Remark string `json:"remark" comment:""`
common.ControlBy
}
func (s *SysDictDataInsertReq) Generate(model *models.SysDictData) {
model.DictCode = s.Id
model.DictSort = s.DictSort
model.DictLabel = s.DictLabel
model.DictValue = s.DictValue
model.DictType = s.DictType
model.CssClass = s.CssClass
model.ListClass = s.ListClass
model.IsDefault = s.IsDefault
model.Status = s.Status
model.Default = s.Default
model.Remark = s.Remark
}
func (s *SysDictDataInsertReq) GetId() interface{} {
return s.Id
}
type SysDictDataUpdateReq struct {
Id int `uri:"dictCode" comment:""`
DictSort int `json:"dictSort" comment:""`
DictLabel string `json:"dictLabel" comment:""`
DictValue string `json:"dictValue" comment:""`
DictType string `json:"dictType" comment:""`
CssClass string `json:"cssClass" comment:""`
ListClass string `json:"listClass" comment:""`
IsDefault string `json:"isDefault" comment:""`
Status int `json:"status" comment:""`
Default string `json:"default" comment:""`
Remark string `json:"remark" comment:""`
common.ControlBy
}
func (s *SysDictDataUpdateReq) Generate(model *models.SysDictData) {
model.DictCode = s.Id
model.DictSort = s.DictSort
model.DictLabel = s.DictLabel
model.DictValue = s.DictValue
model.DictType = s.DictType
model.CssClass = s.CssClass
model.ListClass = s.ListClass
model.IsDefault = s.IsDefault
model.Status = s.Status
model.Default = s.Default
model.Remark = s.Remark
}
func (s *SysDictDataUpdateReq) GetId() interface{} {
return s.Id
}
type SysDictDataGetReq struct {
Id int `uri:"dictCode"`
}
func (s *SysDictDataGetReq) GetId() interface{} {
return s.Id
}
type SysDictDataDeleteReq struct {
Ids []int `json:"ids"`
common.ControlBy `json:"-"`
}
func (s *SysDictDataDeleteReq) GetId() interface{} {
return s.Ids
}
+89
View File
@@ -0,0 +1,89 @@
package dto
import (
"go-admin/app/admin/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
type SysDictTypeGetPageReq struct {
dto.Pagination `search:"-"`
DictId []int `form:"dictId" search:"type:in;column:dict_id;table:sys_dict_type"`
DictName string `form:"dictName" search:"type:icontains;column:dict_name;table:sys_dict_type"`
DictType string `form:"dictType" search:"type:icontains;column:dict_type;table:sys_dict_type"`
Status int `form:"status" search:"type:exact;column:status;table:sys_dict_type"`
}
type SysDictTypeOrder struct {
DictIdOrder string `search:"type:order;column:dict_id;table:sys_dict_type" form:"dictIdOrder"`
}
func (m *SysDictTypeGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysDictTypeInsertReq struct {
Id int `uri:"id"`
DictName string `json:"dictName"`
DictType string `json:"dictType"`
Status int `json:"status"`
Remark string `json:"remark"`
common.ControlBy
}
func (s *SysDictTypeInsertReq) Generate(model *models.SysDictType) {
if s.Id != 0 {
model.ID = s.Id
}
model.DictName = s.DictName
model.DictType = s.DictType
model.Status = s.Status
model.Remark = s.Remark
}
func (s *SysDictTypeInsertReq) GetId() interface{} {
return s.Id
}
type SysDictTypeUpdateReq struct {
Id int `uri:"id"`
DictName string `json:"dictName"`
DictType string `json:"dictType"`
Status int `json:"status"`
Remark string `json:"remark"`
common.ControlBy
}
func (s *SysDictTypeUpdateReq) Generate(model *models.SysDictType) {
if s.Id != 0 {
model.ID = s.Id
}
model.DictName = s.DictName
model.DictType = s.DictType
model.Status = s.Status
model.Remark = s.Remark
}
func (s *SysDictTypeUpdateReq) GetId() interface{} {
return s.Id
}
type SysDictTypeGetReq struct {
Id int `uri:"id"`
}
func (s *SysDictTypeGetReq) GetId() interface{} {
return s.Id
}
type SysDictTypeDeleteReq struct {
Ids []int `json:"ids"`
common.ControlBy
}
func (s *SysDictTypeDeleteReq) GetId() interface{} {
return s.Ids
}
+57
View File
@@ -0,0 +1,57 @@
package dto
import (
"time"
"go-admin/common/dto"
)
type SysLoginLogGetPageReq struct {
dto.Pagination `search:"-"`
Username string `form:"username" search:"type:exact;column:username;table:sys_login_log" comment:"用户名"`
Status string `form:"status" search:"type:exact;column:status;table:sys_login_log" comment:"状态"`
Ipaddr string `form:"ipaddr" search:"type:exact;column:ipaddr;table:sys_login_log" comment:"ip地址"`
LoginLocation string `form:"loginLocation" search:"type:exact;column:login_location;table:sys_login_log" comment:"归属地"`
BeginTime string `form:"beginTime" search:"type:gte;column:ctime;table:sys_login_log" comment:"创建时间"`
EndTime string `form:"endTime" search:"type:lte;column:ctime;table:sys_login_log" comment:"创建时间"`
SysLoginLogOrder
}
type SysLoginLogOrder struct {
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_login_log" form:"createdAtOrder"`
}
func (m *SysLoginLogGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysLoginLogControl struct {
ID int `uri:"Id" comment:"主键"` // 主键
Username string `json:"username" comment:"用户名"`
Status string `json:"status" comment:"状态"`
Ipaddr string `json:"ipaddr" comment:"ip地址"`
LoginLocation string `json:"loginLocation" comment:"归属地"`
Browser string `json:"browser" comment:"浏览器"`
Os string `json:"os" comment:"系统"`
Platform string `json:"platform" comment:"固件"`
LoginTime time.Time `json:"loginTime" comment:"登录时间"`
Remark string `json:"remark" comment:"备注"`
Msg string `json:"msg" comment:"信息"`
}
type SysLoginLogGetReq struct {
Id int `uri:"id"`
}
func (s *SysLoginLogGetReq) GetId() interface{} {
return s.Id
}
// SysLoginLogDeleteReq 功能删除请求参数
type SysLoginLogDeleteReq struct {
Ids []int `json:"ids"`
}
func (s *SysLoginLogDeleteReq) GetId() interface{} {
return s.Ids
}
+159
View File
@@ -0,0 +1,159 @@
package dto
import (
"go-admin/app/admin/models"
common "go-admin/common/models"
"go-admin/common/dto"
)
// SysMenuGetPageReq 列表或者搜索使用结构体
type SysMenuGetPageReq struct {
dto.Pagination `search:"-"`
Title string `form:"title" search:"type:contains;column:title;table:sys_menu" comment:"菜单名称"` // 菜单名称
Visible int `form:"visible" search:"type:exact;column:visible;table:sys_menu" comment:"显示状态"` // 显示状态
}
func (m *SysMenuGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysMenuInsertReq struct {
MenuId int `uri:"id" comment:"编码"` // 编码
MenuName string `form:"menuName" comment:"菜单name"` //菜单name
Title string `form:"title" comment:"显示名称"` //显示名称
Icon string `form:"icon" comment:"图标"` //图标
Path string `form:"path" comment:"路径"` //路径
Paths string `form:"paths" comment:"id路径"` //id路径
MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型
SysApi []models.SysApi `form:"sysApi"`
Apis []int `form:"apis"`
Action string `form:"action" comment:"请求方式"` //请求方式
Permission string `form:"permission" comment:"权限编码"` //权限编码
ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单
NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存
Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑
Component string `form:"component" comment:"组件"` //组件
Sort int `form:"sort" comment:"排序"` //排序
Visible string `form:"visible" comment:"是否显示"` //是否显示
IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame
common.ControlBy
}
func (s *SysMenuInsertReq) Generate(model *models.SysMenu) {
if s.MenuId != 0 {
model.MenuId = s.MenuId
}
model.MenuName = s.MenuName
model.Title = s.Title
model.Icon = s.Icon
model.Path = s.Path
model.Paths = s.Paths
model.MenuType = s.MenuType
model.Action = s.Action
model.SysApi = s.SysApi
model.Permission = s.Permission
model.ParentId = s.ParentId
model.NoCache = s.NoCache
model.Breadcrumb = s.Breadcrumb
model.Component = s.Component
model.Sort = s.Sort
model.Visible = s.Visible
model.IsFrame = s.IsFrame
if s.CreateBy != 0 {
model.CreateBy = s.CreateBy
}
if s.UpdateBy != 0 {
model.UpdateBy = s.UpdateBy
}
}
func (s *SysMenuInsertReq) GetId() interface{} {
return s.MenuId
}
type SysMenuUpdateReq struct {
MenuId int `uri:"id" comment:"编码"` // 编码
MenuName string `form:"menuName" comment:"菜单name"` //菜单name
Title string `form:"title" comment:"显示名称"` //显示名称
Icon string `form:"icon" comment:"图标"` //图标
Path string `form:"path" comment:"路径"` //路径
Paths string `form:"paths" comment:"id路径"` //id路径
MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型
SysApi []models.SysApi `form:"sysApi"`
Apis []int `form:"apis"`
Action string `form:"action" comment:"请求方式"` //请求方式
Permission string `form:"permission" comment:"权限编码"` //权限编码
ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单
NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存
Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑
Component string `form:"component" comment:"组件"` //组件
Sort int `form:"sort" comment:"排序"` //排序
Visible string `form:"visible" comment:"是否显示"` //是否显示
IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame
common.ControlBy
}
func (s *SysMenuUpdateReq) Generate(model *models.SysMenu) {
if s.MenuId != 0 {
model.MenuId = s.MenuId
}
model.MenuName = s.MenuName
model.Title = s.Title
model.Icon = s.Icon
model.Path = s.Path
model.Paths = s.Paths
model.MenuType = s.MenuType
model.Action = s.Action
model.SysApi = s.SysApi
model.Permission = s.Permission
model.ParentId = s.ParentId
model.NoCache = s.NoCache
model.Breadcrumb = s.Breadcrumb
model.Component = s.Component
model.Sort = s.Sort
model.Visible = s.Visible
model.IsFrame = s.IsFrame
if s.CreateBy != 0 {
model.CreateBy = s.CreateBy
}
if s.UpdateBy != 0 {
model.UpdateBy = s.UpdateBy
}
}
func (s *SysMenuUpdateReq) GetId() interface{} {
return s.MenuId
}
type SysMenuGetReq struct {
Id int `uri:"id"`
}
func (s *SysMenuGetReq) GetId() interface{} {
return s.Id
}
type SysMenuDeleteReq struct {
Ids []int `json:"ids"`
common.ControlBy
}
func (s *SysMenuDeleteReq) GetId() interface{} {
return s.Ids
}
type MenuLabel struct {
Id int `json:"id,omitempty" gorm:"-"`
Label string `json:"label,omitempty" gorm:"-"`
Children []MenuLabel `json:"children,omitempty" gorm:"-"`
}
type MenuRole struct {
models.SysMenu
IsSelect bool `json:"is_select" gorm:"-"`
}
type SelectRole struct {
RoleId int `uri:"roleId"`
}
+107
View File
@@ -0,0 +1,107 @@
package dto
import (
"time"
"go-admin/app/admin/models"
"go-admin/common/dto"
"go-admin/common/global"
common "go-admin/common/models"
)
// Deprecated: use global.OperaStatusEnabled / global.OperaStatusDisabled.
// These two names are kept - misspelling and all - because forks import them;
// the values moved to common/global so common/middleware no longer has to
// import this package. See docs/contract.md.
const (
OperaStatusEnabel = global.OperaStatusEnabled // 状态-正常
OperaStatusDisable = global.OperaStatusDisabled // 状态-关闭
)
type SysOperaLogGetPageReq struct {
dto.Pagination `search:"-"`
Title string `form:"title" search:"type:contains;column:title;table:sys_opera_log" comment:"操作模块"`
Method string `form:"method" search:"type:contains;column:method;table:sys_opera_log" comment:"函数"`
RequestMethod string `form:"requestMethod" search:"type:contains;column:request_method;table:sys_opera_log" comment:"请求方式: GET POST PUT DELETE"`
OperUrl string `form:"operUrl" search:"type:contains;column:oper_url;table:sys_opera_log" comment:"访问地址"`
OperIp string `form:"operIp" search:"type:exact;column:oper_ip;table:sys_opera_log" comment:"客户端ip"`
Status int `form:"status" search:"type:exact;column:status;table:sys_opera_log" comment:"状态 1:正常 2:关闭"`
BeginTime string `form:"beginTime" search:"type:gte;column:created_at;table:sys_opera_log" comment:"创建时间"`
EndTime string `form:"endTime" search:"type:lte;column:created_at;table:sys_opera_log" comment:"更新时间"`
SysOperaLogOrder
}
type SysOperaLogOrder struct {
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_opera_log" form:"createdAtOrder"`
}
func (m *SysOperaLogGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysOperaLogControl struct {
ID int `uri:"Id" comment:"编码"` // 编码
Title string `json:"title" comment:"操作模块"`
BusinessType string `json:"businessType" comment:"操作类型"`
BusinessTypes string `json:"businessTypes" comment:""`
Method string `json:"method" comment:"函数"`
RequestMethod string `json:"requestMethod" comment:"请求方式"`
OperatorType string `json:"operatorType" comment:"操作类型"`
OperName string `json:"operName" comment:"操作者"`
DeptName string `json:"deptName" comment:"部门名称"`
OperUrl string `json:"operUrl" comment:"访问地址"`
OperIp string `json:"operIp" comment:"客户端ip"`
OperLocation string `json:"operLocation" comment:"访问位置"`
OperParam string `json:"operParam" comment:"请求参数"`
Status string `json:"status" comment:"操作状态"`
OperTime time.Time `json:"operTime" comment:"操作时间"`
JsonResult string `json:"jsonResult" comment:"返回数据"`
Remark string `json:"remark" comment:"备注"`
LatencyTime string `json:"latencyTime" comment:"耗时"`
UserAgent string `json:"userAgent" comment:"ua"`
}
func (s *SysOperaLogControl) Generate() (*models.SysOperaLog, error) {
return &models.SysOperaLog{
Model: common.Model{Id: s.ID},
Title: s.Title,
BusinessType: s.BusinessType,
BusinessTypes: s.BusinessTypes,
Method: s.Method,
RequestMethod: s.RequestMethod,
OperatorType: s.OperatorType,
OperName: s.OperName,
DeptName: s.DeptName,
OperUrl: s.OperUrl,
OperIp: s.OperIp,
OperLocation: s.OperLocation,
OperParam: s.OperParam,
Status: s.Status,
OperTime: s.OperTime,
JsonResult: s.JsonResult,
Remark: s.Remark,
LatencyTime: s.LatencyTime,
UserAgent: s.UserAgent,
}, nil
}
func (s *SysOperaLogControl) GetId() interface{} {
return s.ID
}
type SysOperaLogGetReq struct {
Id int `uri:"id"`
}
func (s *SysOperaLogGetReq) GetId() interface{} {
return s.Id
}
// SysOperaLogDeleteReq 功能删除请求参数
type SysOperaLogDeleteReq struct {
Ids []int `json:"ids"`
}
func (s *SysOperaLogDeleteReq) GetId() interface{} {
return s.Ids
}
@@ -0,0 +1,24 @@
package dto
import (
"testing"
"go-admin/common/global"
)
// The values moved to common/global so common/middleware would stop importing
// this package; these two names stayed behind as aliases, misspelling and all,
// because forks import them.
//
// If they ever drift apart, rows written through the two spellings land in
// different buckets and the operation-log filter silently misses half of them.
func TestDeprecatedStatusAliasesStillMatch(t *testing.T) {
if OperaStatusEnabel != global.OperaStatusEnabled {
t.Errorf("OperaStatusEnabel = %q, global.OperaStatusEnabled = %q",
OperaStatusEnabel, global.OperaStatusEnabled)
}
if OperaStatusDisable != global.OperaStatusDisabled {
t.Errorf("OperaStatusDisable = %q, global.OperaStatusDisabled = %q",
OperaStatusDisable, global.OperaStatusDisabled)
}
}
+111
View File
@@ -0,0 +1,111 @@
package dto
import (
"go-admin/app/admin/models"
common "go-admin/common/models"
"go-admin/common/dto"
)
// SysPostPageReq 列表或者搜索使用结构体
type SysPostPageReq struct {
dto.Pagination `search:"-"`
PostId int `form:"postId" search:"type:exact;column:post_id;table:sys_post" comment:"id"` // id
PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post" comment:"名称"` // 名称
PostCode string `form:"postCode" search:"type:contains;column:post_code;table:sys_post" comment:"编码"` // 编码
Sort int `form:"sort" search:"type:exact;column:sort;table:sys_post" comment:"排序"` // 排序
Status int `form:"status" search:"type:exact;column:status;table:sys_post" comment:"状态"` // 状态
Remark string `form:"remark" search:"type:exact;column:remark;table:sys_post" comment:"备注"` // 备注
}
func (m *SysPostPageReq) GetNeedSearch() interface{} {
return *m
}
// SysPostInsertReq 增使用的结构体
type SysPostInsertReq struct {
PostId int `uri:"id" comment:"id"`
PostName string `form:"postName" comment:"名称"`
PostCode string `form:"postCode" comment:"编码"`
Sort int `form:"sort" comment:"排序"`
Status int `form:"status" comment:"状态"`
Remark string `form:"remark" comment:"备注"`
common.ControlBy
}
func (s *SysPostInsertReq) Generate(model *models.SysPost) {
model.PostName = s.PostName
model.PostCode = s.PostCode
model.Sort = s.Sort
model.Status = s.Status
model.Remark = s.Remark
if s.ControlBy.UpdateBy != 0 {
model.UpdateBy = s.UpdateBy
}
if s.ControlBy.CreateBy != 0 {
model.CreateBy = s.CreateBy
}
}
// GetId 获取数据对应的ID
func (s *SysPostInsertReq) GetId() interface{} {
return s.PostId
}
// SysPostUpdateReq 改使用的结构体
type SysPostUpdateReq struct {
PostId int `uri:"id" comment:"id"`
PostName string `form:"postName" comment:"名称"`
PostCode string `form:"postCode" comment:"编码"`
Sort int `form:"sort" comment:"排序"`
Status int `form:"status" comment:"状态"`
Remark string `form:"remark" comment:"备注"`
common.ControlBy
}
func (s *SysPostUpdateReq) Generate(model *models.SysPost) {
model.PostId = s.PostId
model.PostName = s.PostName
model.PostCode = s.PostCode
model.Sort = s.Sort
model.Status = s.Status
model.Remark = s.Remark
if s.ControlBy.UpdateBy != 0 {
model.UpdateBy = s.UpdateBy
}
if s.ControlBy.CreateBy != 0 {
model.CreateBy = s.CreateBy
}
}
func (s *SysPostUpdateReq) GetId() interface{} {
return s.PostId
}
// SysPostGetReq 获取单个的结构体
type SysPostGetReq struct {
Id int `uri:"id"`
}
func (s *SysPostGetReq) GetId() interface{} {
return s.Id
}
// SysPostDeleteReq 删除的结构体
type SysPostDeleteReq struct {
Ids []int `json:"ids"`
common.ControlBy
}
func (s *SysPostDeleteReq) Generate(model *models.SysPost) {
if s.ControlBy.UpdateBy != 0 {
model.UpdateBy = s.UpdateBy
}
if s.ControlBy.CreateBy != 0 {
model.CreateBy = s.CreateBy
}
}
func (s *SysPostDeleteReq) GetId() interface{} {
return s.Ids
}
+164
View File
@@ -0,0 +1,164 @@
package dto
import (
"go-admin/app/admin/models"
common "go-admin/common/models"
"go-admin/common/dto"
)
type SysRoleGetPageReq struct {
dto.Pagination `search:"-"`
RoleId int `form:"roleId" search:"type:exact;column:role_id;table:sys_role" comment:"角色编码"` // 角色编码
RoleName string `form:"roleName" search:"type:exact;column:role_name;table:sys_role" comment:"角色名称"` // 角色名称
Status string `form:"status" search:"type:exact;column:status;table:sys_role" comment:"状态"` // 状态
RoleKey string `form:"roleKey" search:"type:exact;column:role_key;table:sys_role" comment:"角色代码"` // 角色代码
RoleSort int `form:"roleSort" search:"type:exact;column:role_sort;table:sys_role" comment:"角色排序"` // 角色排序
Flag string `form:"flag" search:"type:exact;column:flag;table:sys_role" comment:"标记"` // 标记
Remark string `form:"remark" search:"type:exact;column:remark;table:sys_role" comment:"备注"` // 备注
Admin bool `form:"admin" search:"type:exact;column:admin;table:sys_role" comment:"是否管理员"`
DataScope string `form:"dataScope" search:"type:exact;column:data_scope;table:sys_role" comment:"是否管理员"`
}
type SysRoleOrder struct {
RoleIdOrder string `search:"type:order;column:role_id;table:sys_role" form:"roleIdOrder"`
RoleNameOrder string `search:"type:order;column:role_name;table:sys_role" form:"roleNameOrder"`
RoleSortOrder string `search:"type:order;column:role_sort;table:sys_role" form:"usernameOrder"`
StatusOrder string `search:"type:order;column:status;table:sys_role" form:"statusOrder"`
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_role" form:"createdAtOrder"`
}
func (m *SysRoleGetPageReq) GetNeedSearch() interface{} {
return *m
}
type SysRoleInsertReq struct {
RoleId int `uri:"id" comment:"角色编码"` // 角色编码
RoleName string `form:"roleName" comment:"角色名称"` // 角色名称
Status string `form:"status" comment:"状态"` // 状态 1禁用 2正常
RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码
RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序
Flag string `form:"flag" comment:"标记"` // 标记
Remark string `form:"remark" comment:"备注"` // 备注
Admin bool `form:"admin" comment:"是否管理员"`
DataScope string `form:"dataScope" vd:"$=='1'||$=='2'||$=='3'||$=='4'||$=='5'"` // must be one of actions.DataScope{All,Custom,Dept,DeptTree,Self}; PRD 006 F14/H2
SysMenu []models.SysMenu `form:"sysMenu"`
MenuIds []int `form:"menuIds"`
SysDept []models.SysDept `form:"sysDept"`
DeptIds []int `form:"deptIds"`
common.ControlBy
}
func (s *SysRoleInsertReq) Generate(model *models.SysRole) {
if s.RoleId != 0 {
model.RoleId = s.RoleId
}
model.RoleName = s.RoleName
model.Status = s.Status
model.RoleKey = s.RoleKey
model.RoleSort = s.RoleSort
model.Flag = s.Flag
model.Remark = s.Remark
model.Admin = s.Admin
model.DataScope = s.DataScope
model.SysMenu = &s.SysMenu
model.SysDept = s.SysDept
}
func (s *SysRoleInsertReq) GetId() interface{} {
return s.RoleId
}
type SysRoleUpdateReq struct {
RoleId int `uri:"id" comment:"角色编码"` // 角色编码
RoleName string `form:"roleName" comment:"角色名称"` // 角色名称
Status string `form:"status" comment:"状态"` // 状态
RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码
RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序
Flag string `form:"flag" comment:"标记"` // 标记
Remark string `form:"remark" comment:"备注"` // 备注
Admin bool `form:"admin" comment:"是否管理员"`
DataScope string `form:"dataScope" vd:"$=='1'||$=='2'||$=='3'||$=='4'||$=='5'"` // must be one of actions.DataScope{All,Custom,Dept,DeptTree,Self}; PRD 006 F14/H2
SysMenu []models.SysMenu `form:"sysMenu"`
MenuIds []int `form:"menuIds"`
SysDept []models.SysDept `form:"sysDept"`
DeptIds []int `form:"deptIds"`
common.ControlBy
}
func (s *SysRoleUpdateReq) Generate(model *models.SysRole) {
if s.RoleId != 0 {
model.RoleId = s.RoleId
}
model.RoleName = s.RoleName
model.Status = s.Status
model.RoleKey = s.RoleKey
model.RoleSort = s.RoleSort
model.Flag = s.Flag
model.Remark = s.Remark
model.Admin = s.Admin
model.DataScope = s.DataScope
model.SysMenu = &s.SysMenu
model.SysDept = s.SysDept
}
func (s *SysRoleUpdateReq) GetId() interface{} {
return s.RoleId
}
type UpdateStatusReq struct {
RoleId int `form:"roleId" comment:"角色编码"` // 角色编码
Status string `form:"status" comment:"状态"` // 状态
common.ControlBy
}
func (s *UpdateStatusReq) Generate(model *models.SysRole) {
if s.RoleId != 0 {
model.RoleId = s.RoleId
}
model.Status = s.Status
}
func (s *UpdateStatusReq) GetId() interface{} {
return s.RoleId
}
type SysRoleByName struct {
RoleName string `form:"role"` // 角色编码
}
type SysRoleGetReq struct {
Id int `uri:"id"`
}
func (s *SysRoleGetReq) GetId() interface{} {
return s.Id
}
type SysRoleDeleteReq struct {
Ids []int `json:"ids"`
}
func (s *SysRoleDeleteReq) GetId() interface{} {
return s.Ids
}
// RoleDataScopeReq 角色数据权限修改
type RoleDataScopeReq struct {
RoleId int `json:"roleId" binding:"required"`
DataScope string `json:"dataScope" binding:"required" vd:"$=='1'||$=='2'||$=='3'||$=='4'||$=='5'"` // must be one of actions.DataScope{All,Custom,Dept,DeptTree,Self}; PRD 006 F14/H2
DeptIds []int `json:"deptIds"`
}
func (s *RoleDataScopeReq) Generate(model *models.SysRole) {
if s.RoleId != 0 {
model.RoleId = s.RoleId
}
model.DataScope = s.DataScope
model.DeptIds = s.DeptIds
}
type DeptIdList struct {
DeptId int `json:"DeptId"`
}
@@ -0,0 +1,64 @@
package dto
import (
"testing"
vd "github.com/bytedance/go-tagexpr/v2/validator"
)
// api.Bind calls vd.Validate unconditionally on every request, regardless of
// which binding stage ran, so a vd tag on DataScope is enough to reject
// anything actions.Permission's fail-closed default would otherwise have to
// deal with. PRD 006 F14/H2 named this the real trigger for the default
// branch: SysRoleInsertReq.DataScope had no validation at all, so leaving
// dataScope out of a create-role request wrote an empty string straight to
// sys_role.
func TestDataScopeRejectsWhatPermissionCannotRecognize(t *testing.T) {
invalid := []string{"", "0", "6", "all", " 1", "1 "}
valid := []string{"1", "2", "3", "4", "5"}
t.Run("SysRoleInsertReq", func(t *testing.T) {
for _, s := range invalid {
req := SysRoleInsertReq{RoleName: "r", RoleKey: "r", DataScope: s}
if err := vd.Validate(&req); err == nil {
t.Errorf("DataScope %q was accepted, want rejected", s)
}
}
for _, s := range valid {
req := SysRoleInsertReq{RoleName: "r", RoleKey: "r", DataScope: s}
if err := vd.Validate(&req); err != nil {
t.Errorf("DataScope %q was rejected: %v", s, err)
}
}
})
t.Run("SysRoleUpdateReq", func(t *testing.T) {
for _, s := range invalid {
req := SysRoleUpdateReq{RoleName: "r", RoleKey: "r", DataScope: s}
if err := vd.Validate(&req); err == nil {
t.Errorf("DataScope %q was accepted, want rejected", s)
}
}
for _, s := range valid {
req := SysRoleUpdateReq{RoleName: "r", RoleKey: "r", DataScope: s}
if err := vd.Validate(&req); err != nil {
t.Errorf("DataScope %q was rejected: %v", s, err)
}
}
})
t.Run("RoleDataScopeReq", func(t *testing.T) {
for _, s := range invalid {
req := RoleDataScopeReq{RoleId: 1, DataScope: s}
if err := vd.Validate(&req); err == nil {
t.Errorf("DataScope %q was accepted, want rejected", s)
}
}
for _, s := range valid {
req := RoleDataScopeReq{RoleId: 1, DataScope: s}
if err := vd.Validate(&req); err != nil {
t.Errorf("DataScope %q was rejected: %v", s, err)
}
}
})
}
+189
View File
@@ -0,0 +1,189 @@
package dto
import (
"go-admin/app/admin/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
type SysUserGetPageReq struct {
dto.Pagination `search:"-"`
UserId int `form:"userId" search:"type:exact;column:user_id;table:sys_user" comment:"用户ID"`
Username string `form:"username" search:"type:contains;column:username;table:sys_user" comment:"用户名"`
NickName string `form:"nickName" search:"type:contains;column:nick_name;table:sys_user" comment:"昵称"`
Phone string `form:"phone" search:"type:contains;column:phone;table:sys_user" comment:"手机号"`
RoleId string `form:"roleId" search:"type:exact;column:role_id;table:sys_user" comment:"角色ID"`
Sex string `form:"sex" search:"type:exact;column:sex;table:sys_user" comment:"性别"`
Email string `form:"email" search:"type:contains;column:email;table:sys_user" comment:"邮箱"`
PostId string `form:"postId" search:"type:exact;column:post_id;table:sys_user" comment:"岗位"`
Status string `form:"status" search:"type:exact;column:status;table:sys_user" comment:"状态"`
DeptJoin `search:"type:left;on:dept_id:dept_id;table:sys_user;join:sys_dept"`
SysUserOrder
}
type SysUserOrder struct {
UserIdOrder string `search:"type:order;column:user_id;table:sys_user" form:"userIdOrder"`
UsernameOrder string `search:"type:order;column:username;table:sys_user" form:"usernameOrder"`
StatusOrder string `search:"type:order;column:status;table:sys_user" form:"statusOrder"`
CreatedAtOrder string `search:"type:order;column:created_at;table:sys_user" form:"createdAtOrder"`
}
type DeptJoin struct {
DeptId string `search:"type:contains;column:dept_path;table:sys_dept" form:"deptId"`
}
func (m *SysUserGetPageReq) GetNeedSearch() interface{} {
return *m
}
type ResetSysUserPwdReq struct {
UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID
Password string `json:"password" comment:"密码" vd:"len($)>0"`
common.ControlBy
}
func (s *ResetSysUserPwdReq) GetId() interface{} {
return s.UserId
}
func (s *ResetSysUserPwdReq) Generate(model *models.SysUser) {
if s.UserId != 0 {
model.UserId = s.UserId
}
model.Password = s.Password
}
type UpdateSysUserAvatarReq struct {
UserId int `json:"userId" comment:"用户ID" vd:"len($)>0"` // 用户ID
Avatar string `json:"avatar" comment:"头像" vd:"len($)>0"`
common.ControlBy
}
func (s *UpdateSysUserAvatarReq) GetId() interface{} {
return s.UserId
}
func (s *UpdateSysUserAvatarReq) Generate(model *models.SysUser) {
if s.UserId != 0 {
model.UserId = s.UserId
}
model.Avatar = s.Avatar
}
type UpdateSysUserStatusReq struct {
UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID
Status string `json:"status" comment:"状态" vd:"len($)>0"`
common.ControlBy
}
func (s *UpdateSysUserStatusReq) GetId() interface{} {
return s.UserId
}
func (s *UpdateSysUserStatusReq) Generate(model *models.SysUser) {
if s.UserId != 0 {
model.UserId = s.UserId
}
model.Status = s.Status
}
type SysUserInsertReq struct {
UserId int `json:"userId" comment:"用户ID"` // 用户ID
Username string `json:"username" comment:"用户名" vd:"len($)>0"`
Password string `json:"password" comment:"密码"`
NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"`
Phone string `json:"phone" comment:"手机号" vd:"len($)>0"`
RoleId int `json:"roleId" comment:"角色ID"`
Avatar string `json:"avatar" comment:"头像"`
Sex string `json:"sex" comment:"性别"`
Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"`
DeptId int `json:"deptId" comment:"部门" vd:"$>0"`
PostId int `json:"postId" comment:"岗位"`
Remark string `json:"remark" comment:"备注"`
Status string `json:"status" comment:"状态" vd:"len($)>0" default:"1"`
common.ControlBy
}
func (s *SysUserInsertReq) Generate(model *models.SysUser) {
if s.UserId != 0 {
model.UserId = s.UserId
}
model.Username = s.Username
model.Password = s.Password
model.NickName = s.NickName
model.Phone = s.Phone
model.RoleId = s.RoleId
model.Avatar = s.Avatar
model.Sex = s.Sex
model.Email = s.Email
model.DeptId = s.DeptId
model.PostId = s.PostId
model.Remark = s.Remark
model.Status = s.Status
model.CreateBy = s.CreateBy
}
func (s *SysUserInsertReq) GetId() interface{} {
return s.UserId
}
type SysUserUpdateReq struct {
UserId int `json:"userId" comment:"用户ID"` // 用户ID
Username string `json:"username" comment:"用户名" vd:"len($)>0"`
NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"`
Phone string `json:"phone" comment:"手机号" vd:"len($)>0"`
RoleId int `json:"roleId" comment:"角色ID"`
Avatar string `json:"avatar" comment:"头像"`
Sex string `json:"sex" comment:"性别"`
Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"`
DeptId int `json:"deptId" comment:"部门" vd:"$>0"`
PostId int `json:"postId" comment:"岗位"`
Remark string `json:"remark" comment:"备注"`
Status string `json:"status" comment:"状态" default:"1"`
common.ControlBy
}
func (s *SysUserUpdateReq) Generate(model *models.SysUser) {
if s.UserId != 0 {
model.UserId = s.UserId
}
model.Username = s.Username
model.NickName = s.NickName
model.Phone = s.Phone
model.RoleId = s.RoleId
model.Avatar = s.Avatar
model.Sex = s.Sex
model.Email = s.Email
model.DeptId = s.DeptId
model.PostId = s.PostId
model.Remark = s.Remark
model.Status = s.Status
}
func (s *SysUserUpdateReq) GetId() interface{} {
return s.UserId
}
type SysUserById struct {
dto.ObjectById
common.ControlBy
}
func (s *SysUserById) GetId() interface{} {
if len(s.Ids) > 0 {
s.Ids = append(s.Ids, s.Id)
return s.Ids
}
return s.Id
}
func (s *SysUserById) GenerateM() (common.ActiveRecord, error) {
return &models.SysUser{}, nil
}
// PassWord 密码
type PassWord struct {
NewPassword string `json:"newPassword" vd:"len($)>0"`
OldPassword string `json:"oldPassword" vd:"len($)>0"`
}
+308
View File
@@ -0,0 +1,308 @@
package service
import (
"errors"
"fmt"
"strconv"
"strings"
"gorm.io/gorm"
contractmodels "github.com/go-admin-team/go-admin-core/v2/sdk/contract/models"
"github.com/go-admin-team/go-admin-core/v2/sdk/contract/seed"
"go-admin/app/admin/models"
)
// adminSeeder is go-admin's own implementation of seed.Seeder: it turns the
// MenuSpec/ApiSpec values a third-party application asks for into rows
// across the four tables a visible, working menu entry needs - sys_api,
// sys_menu, sys_menu_api_rule, and sys_role_menu/casbin_rule - following the
// same shape cmd/migrate/migration/version/1786700001000_demo_menu.go
// already hand-writes for the host's own demo module.
//
// See go-admin-core's docs/contract.md, "Application-supplied menu and API
// entries", for the requirements this satisfies, and the security note on
// seed.Seeder for what this boundary does and does not protect against: an
// application already holds the same *gorm.DB this receives and could write
// sys_menu/sys_api/casbin_rule directly, bypassing this entirely.
type adminSeeder struct{}
func init() {
seed.RegisterSeeder(adminSeeder{})
}
// adminRoleKey is the role every seeded menu is granted to. This mirrors
// 1786700001000_demo_menu.go's own convention rather than inventing a
// second one: MenuSpec carries no "which roles should see this" field for a
// Seeder to consult instead, and admin is the one role guaranteed to exist
// once the framework's own seed data has run.
const adminRoleKey = "admin"
// menuSortRange is what sys_menu.sort's column type actually holds.
//
// sort is `gorm:"size:4"`, which MySQL builds as a tinyint (-128..127);
// sqlite ignores the width and accepts anything, so this only ever surfaces
// on a real install, mid-migration, as Error 1264 - by which point the
// migration has already run other, non-transactional DDL that will not be
// retried. tools/checksilent's menu-sort-overflow check catches this for
// every MenuSpec-shaped literal committed to this repository, but it walks
// the repository's own source tree: a third-party application living in the
// module cache is invisible to it. This is the equivalent check for that
// application, run when its migration actually calls SeedMenus rather than
// never.
const (
menuSortMin = -128
menuSortMax = 127
)
func (adminSeeder) SeedMenus(tx *gorm.DB, appCode string, menus []seed.MenuSpec, apis []seed.ApiSpec) error {
apiRows, err := seedApis(tx, appCode, apis)
if err != nil {
return fmt.Errorf("seed: app %q: apis: %w", appCode, err)
}
menuIDs, err := seedMenuTree(tx, appCode, menus, apiRows)
if err != nil {
return fmt.Errorf("seed: app %q: menus: %w", appCode, err)
}
// Not `len(menuIDs) == 0`: grantToAdminRole grants two independent
// things, and an application is free to register apis without menus -
// endpoints another service calls, or a UI mounted somewhere else.
// Skipping the whole call on an empty menu list wrote the sys_api rows
// and then no casbin rule for them, so those endpoints were denied to
// everyone, admin included, with a migration that reported success.
if len(menuIDs) == 0 && len(apiRows) == 0 {
return nil
}
return grantToAdminRole(tx, menuIDs, apiRows)
}
// seedApis writes one sys_api row per ApiSpec and returns them keyed by
// ApiSpec.Code, so seedMenuTree can resolve a MenuSpec's ApiCodes into the
// rows sys_menu_api_rule needs to reference.
//
// sys_api.id is left to autoincrement rather than assigned by the caller,
// unlike 1786700001000_demo_menu.go's hand-picked ids: that migration is
// the one file tools/checksilent's menu-id-collision check can see, because
// it lives in this repository; nothing plays that role for a third-party
// application's ids in the module cache. Never accepting a caller-chosen id
// here removes the collision this Seeder has no way to detect instead of
// trying to detect it after the fact.
func seedApis(tx *gorm.DB, appCode string, apis []seed.ApiSpec) (map[string]models.SysApi, error) {
seen := make(map[string]bool, len(apis))
rows := make(map[string]models.SysApi, len(apis))
for _, a := range apis {
if a.Code == "" {
return nil, errors.New("ApiSpec.Code must not be empty")
}
if seen[a.Code] {
return nil, fmt.Errorf("duplicate ApiSpec.Code %q", a.Code)
}
seen[a.Code] = true
row := models.SysApi{
Handle: a.Handle,
Title: a.Title,
Path: a.Path,
Action: a.Method,
Type: "SYS",
AppCode: appCode,
}
if err := tx.Create(&row).Error; err != nil {
return nil, fmt.Errorf("api %q: %w", a.Code, err)
}
rows[a.Code] = row
}
return rows, nil
}
// seedMenuTree writes one sys_menu row per MenuSpec, resolving Parent/Code
// references into parent_id/paths, and returns every menu id created so the
// caller can grant them to a role.
//
// Specs do not have to be given in parent-before-child order: this makes
// repeated passes over the remaining specs, creating whichever ones have
// their Parent (if any) already created, until every spec is placed. A
// spec whose Parent never resolves - naming a Code missing from this call,
// or only reachable through a cycle - stops making progress and is reported
// rather than looping forever.
func seedMenuTree(tx *gorm.DB, appCode string, specs []seed.MenuSpec, apiRows map[string]models.SysApi) ([]int, error) {
byCode := make(map[string]seed.MenuSpec, len(specs))
for _, s := range specs {
if s.Code == "" {
return nil, errors.New("MenuSpec.Code must not be empty")
}
if _, dup := byCode[s.Code]; dup {
return nil, fmt.Errorf("duplicate MenuSpec.Code %q", s.Code)
}
if err := validateMenuSpec(s); err != nil {
return nil, fmt.Errorf("%q: %w", s.Code, err)
}
byCode[s.Code] = s
}
created := make(map[string]models.SysMenu, len(specs))
ids := make([]int, 0, len(specs))
for len(created) < len(specs) {
progressed := false
for _, s := range specs {
if _, done := created[s.Code]; done {
continue
}
var parentRow models.SysMenu
if s.Parent != "" {
parent, ok := created[s.Parent]
if !ok {
if _, exists := byCode[s.Parent]; !exists {
return nil, fmt.Errorf("%q: Parent %q is not a Code in this call", s.Code, s.Parent)
}
continue // s.Parent exists but has not been created yet; retry next pass
}
parentRow = parent
}
row := models.SysMenu{
MenuName: menuName(appCode, s.Code),
Title: s.Title,
Icon: s.Icon,
Path: s.Path,
MenuType: s.Kind,
Permission: s.Permission,
ParentId: parentRow.MenuId,
Component: s.Component,
Sort: s.Sort,
// Visible "0" is shown, not hidden - the same defaults
// 1786700001000_demo_menu.go seeds its own menu with. A
// freshly installed application's menu should not need an
// administrator to first find and unhide it.
Visible: "0",
IsFrame: "1",
AppCode: appCode,
}
for _, code := range s.ApiCodes {
api, ok := apiRows[code]
if !ok {
return nil, fmt.Errorf("%q: ApiCodes references %q, which is not an ApiSpec.Code in this call", s.Code, code)
}
// The full row, not just {Id: api.Id}: gorm's many2many
// association save upserts an associated row whose primary
// key is already set, so a stub carrying only Id would
// overwrite every other column of an sys_api row this same
// call just wrote with zero values.
row.SysApi = append(row.SysApi, api)
}
if err := tx.Create(&row).Error; err != nil {
return nil, fmt.Errorf("%q: %w", s.Code, err)
}
// paths is a materialized path from the root ("/0"), built from
// ids that only exist once the row above is created - the same
// two-step create-then-update 1786700001000_demo_menu.go's
// hand-assigned ids let it do in one literal, sequenced here
// instead.
if s.Parent == "" {
row.Paths = "/0/" + strconv.Itoa(row.MenuId)
} else {
row.Paths = parentRow.Paths + "/" + strconv.Itoa(row.MenuId)
}
if err := tx.Model(&models.SysMenu{}).Where("menu_id = ?", row.MenuId).
Update("paths", row.Paths).Error; err != nil {
return nil, fmt.Errorf("%q: writing paths: %w", s.Code, err)
}
created[s.Code] = row
ids = append(ids, row.MenuId)
progressed = true
}
if !progressed {
return nil, fmt.Errorf("unresolved Parent reference(s) among %d remaining spec(s); check for a cycle", len(specs)-len(created))
}
}
return ids, nil
}
// validateMenuSpec rejects the malformed input tools/checksilent's
// menu-sort-overflow and Kind-adjacent checks would catch for an in-tree
// seed but cannot for a third-party application's - see menuSortRange's doc
// comment.
func validateMenuSpec(s seed.MenuSpec) error {
switch s.Kind {
case contractmodels.Directory, contractmodels.Menu, contractmodels.Button:
default:
return fmt.Errorf("Kind %q is not one of Directory/Menu/Button", s.Kind)
}
if s.Sort < menuSortMin || s.Sort > menuSortMax {
return fmt.Errorf("Sort %d does not fit sys_menu.sort's tinyint column (%d..%d)", s.Sort, menuSortMin, menuSortMax)
}
return nil
}
// menuName synthesizes sys_menu.menu_name from appCode and the spec's Code,
// since MenuSpec carries no field of its own for it - contract/seed's
// package doc says a MenuSpec is what rendering a menu and checking a
// button permission need, not a mirror of sys_menu's columns.
//
// PascalCasing both and concatenating them, rather than using Code alone,
// is what keeps two applications that both picked the plain word "list" as
// a Code from producing the identical menu_name: the frontend's keep-alive
// cache matches a route by this exact string, not by (appCode, Code), so a
// collision there is a UI bug, not a database error, and nothing else here
// would ever surface it.
func menuName(appCode, code string) string {
return pascalCase(appCode) + pascalCase(code)
}
func pascalCase(s string) string {
var b strings.Builder
for _, part := range strings.FieldsFunc(s, func(r rune) bool { return r == '-' || r == '_' }) {
b.WriteString(strings.ToUpper(part[:1]))
b.WriteString(part[1:])
}
return b.String()
}
// grantToAdminRole is sys_role_menu and casbin_rule: the two tables
// go-admin-core's contract.md requires alongside sys_menu/sys_api, without
// which a seeded menu is invisible to every role and its apis are
// authorized for no one.
//
// It follows 1786700001000_demo_menu.go's exact pattern, including
// tolerating a missing admin role: a database that has not yet run the
// framework's own seed data (config/db.sql, inside 1599190683659_tables.go)
// has nothing to grant to yet, and namespacedKey's ordering guarantee - every
// framework migration sorts before every app-prefixed one - means that
// should not happen in practice, but failing this call over it would be
// worse than a menu with no grant yet.
func grantToAdminRole(tx *gorm.DB, menuIDs []int, apiRows map[string]models.SysApi) error {
var role models.SysRole
if err := tx.Where("role_key = ?", adminRoleKey).First(&role).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err
}
for _, id := range menuIDs {
if err := tx.Exec(
"INSERT INTO sys_role_menu (role_id, menu_id) SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu WHERE role_id = ? AND menu_id = ?)",
role.RoleId, id, role.RoleId, id,
).Error; err != nil {
return err
}
}
for _, a := range apiRows {
if err := tx.Exec(
"INSERT INTO casbin_rule (ptype, v0, v1, v2, v3, v4, v5) SELECT 'p', ?, ?, ?, '', '', '' WHERE NOT EXISTS (SELECT 1 FROM casbin_rule WHERE ptype='p' AND v0=? AND v1=? AND v2=?)",
role.RoleKey, a.Path, a.Action, role.RoleKey, a.Path, a.Action,
).Error; err != nil {
return err
}
}
return nil
}
+291
View File
@@ -0,0 +1,291 @@
package service
import (
"errors"
"strconv"
"strings"
"testing"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
contractmodels "github.com/go-admin-team/go-admin-core/v2/sdk/contract/models"
"github.com/go-admin-team/go-admin-core/v2/sdk/contract/seed"
"go-admin/app/admin/models"
)
// newSeedTestDB builds the tables adminSeeder.SeedMenus writes to. sys_menu,
// sys_api, sys_role and sys_role_menu (GORM's own join table for
// SysRole.SysMenu) come from AutoMigrate; casbin_rule does not have a GORM
// model anywhere in this codebase - see 1786700001000_demo_menu.go's own
// comment on why models.CasbinRule (-> sys_casbin_rule) is the wrong table -
// so it is created directly, matching the columns grantToAdminRole's INSERT
// addresses.
func newSeedTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatalf("open: %v", err)
}
if err := db.AutoMigrate(&models.SysMenu{}, &models.SysApi{}, &models.SysRole{}); err != nil {
t.Fatalf("automigrate: %v", err)
}
if err := db.Exec(`CREATE TABLE casbin_rule (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ptype TEXT, v0 TEXT, v1 TEXT, v2 TEXT, v3 TEXT, v4 TEXT, v5 TEXT
)`).Error; err != nil {
t.Fatalf("create casbin_rule: %v", err)
}
return db
}
func seedAdminRole(t *testing.T, db *gorm.DB) models.SysRole {
t.Helper()
role := models.SysRole{RoleName: "Administrator", RoleKey: adminRoleKey}
if err := db.Create(&role).Error; err != nil {
t.Fatalf("seed admin role: %v", err)
}
return role
}
// This is the acceptance case go-admin-core's docs/contract.md requires: one
// SeedMenus call populates all four tables a visible, working menu entry
// needs, every row tagged with the appCode it was called with, and the
// parent/child tree resolved into sys_menu's parent_id/paths.
func TestSeedMenusPopulatesAllFourTables(t *testing.T) {
db := newSeedTestDB(t)
seedAdminRole(t, db)
menus := []seed.MenuSpec{
{Code: "dir", Kind: contractmodels.Directory, Title: "Order Example", Path: "/apps/order", Component: "Layout", Sort: 10},
{Code: "list", Parent: "dir", Kind: contractmodels.Menu, Title: "Orders", Path: "list", Component: "apps/order/order/index", Sort: 1, ApiCodes: []string{"list"}},
{Code: "btn-create", Parent: "list", Kind: contractmodels.Button, Title: "Create", Permission: "order:order:create", Sort: 1},
}
apis := []seed.ApiSpec{
{Code: "list", Title: "Order list", Path: "/api/v1/order", Method: "GET", Handle: "apis.Order.GetPage-fm"},
}
err := db.Transaction(func(tx *gorm.DB) error {
return adminSeeder{}.SeedMenus(tx, "order", menus, apis)
})
if err != nil {
t.Fatalf("SeedMenus: %v", err)
}
var apiRows []models.SysApi
if err := db.Find(&apiRows).Error; err != nil {
t.Fatal(err)
}
if len(apiRows) != 1 || apiRows[0].AppCode != "order" || apiRows[0].Path != "/api/v1/order" {
t.Fatalf("sys_api = %+v", apiRows)
}
var menuRows []models.SysMenu
if err := db.Order("sort").Find(&menuRows).Error; err != nil {
t.Fatal(err)
}
if len(menuRows) != 3 {
t.Fatalf("sys_menu has %d rows, want 3: %+v", len(menuRows), menuRows)
}
byName := map[string]models.SysMenu{}
for _, m := range menuRows {
if m.AppCode != "order" {
t.Errorf("menu %q app_code = %q, want order", m.MenuName, m.AppCode)
}
byName[m.MenuName] = m
}
dir, ok := byName[menuName("order", "dir")]
if !ok || dir.ParentId != 0 || dir.Paths != "/0/"+strconv.Itoa(dir.MenuId) {
t.Fatalf("dir menu = %+v", dir)
}
list, ok := byName[menuName("order", "list")]
if !ok || list.ParentId != dir.MenuId || list.Paths != dir.Paths+"/"+strconv.Itoa(list.MenuId) {
t.Fatalf("list menu = %+v (dir=%+v)", list, dir)
}
btn, ok := byName[menuName("order", "btn-create")]
if !ok || btn.ParentId != list.MenuId {
t.Fatalf("btn menu = %+v (list=%+v)", btn, list)
}
// sys_menu_api_rule: gorm's own many2many join table for SysMenu.SysApi.
var joinCount int64
if err := db.Table("sys_menu_api_rule").
Where("sys_menu_menu_id = ? AND sys_api_id = ?", list.MenuId, apiRows[0].Id).
Count(&joinCount).Error; err != nil {
t.Fatal(err)
}
if joinCount != 1 {
t.Errorf("sys_menu_api_rule has %d row(s) linking list to its api, want 1", joinCount)
}
// sys_role_menu: every seeded menu granted to the admin role.
var roleMenuCount int64
if err := db.Table("sys_role_menu").Count(&roleMenuCount).Error; err != nil {
t.Fatal(err)
}
if roleMenuCount != 3 {
t.Errorf("sys_role_menu has %d row(s), want 3 (one per seeded menu)", roleMenuCount)
}
// casbin_rule: the api's path/method granted to the admin role.
var casbinCount int64
if err := db.Table("casbin_rule").
Where("ptype = 'p' AND v0 = ? AND v1 = ? AND v2 = ?", adminRoleKey, "/api/v1/order", "GET").
Count(&casbinCount).Error; err != nil {
t.Fatal(err)
}
if casbinCount != 1 {
t.Errorf("casbin_rule has %d matching row(s), want 1", casbinCount)
}
}
// A database that has not run the framework's own seed data yet (no admin
// role) must not fail SeedMenus - 1786700001000_demo_menu.go tolerates
// exactly the same condition for the host's own demo module.
func TestSeedMenusToleratesMissingAdminRole(t *testing.T) {
db := newSeedTestDB(t)
err := db.Transaction(func(tx *gorm.DB) error {
return adminSeeder{}.SeedMenus(tx, "order", []seed.MenuSpec{
{Code: "dir", Kind: contractmodels.Directory, Title: "Order"},
}, nil)
})
if err != nil {
t.Fatalf("SeedMenus: %v", err)
}
var roleMenuCount int64
if err := db.Table("sys_role_menu").Count(&roleMenuCount).Error; err != nil {
t.Fatal(err)
}
if roleMenuCount != 0 {
t.Errorf("sys_role_menu has %d row(s) with no role to grant to", roleMenuCount)
}
}
func TestSeedMenusRejectsMalformedSpecs(t *testing.T) {
cases := []struct {
name string
menus []seed.MenuSpec
apis []seed.ApiSpec
want string
}{
{
name: "duplicate menu code",
menus: []seed.MenuSpec{{Code: "a", Kind: contractmodels.Directory}, {Code: "a", Kind: contractmodels.Directory}},
want: `duplicate MenuSpec.Code "a"`,
},
{
name: "unresolved parent",
menus: []seed.MenuSpec{{Code: "a", Parent: "missing", Kind: contractmodels.Menu}},
want: `Parent "missing" is not a Code in this call`,
},
{
name: "unresolved api code",
menus: []seed.MenuSpec{{Code: "a", Kind: contractmodels.Menu, ApiCodes: []string{"missing"}}},
want: `ApiCodes references "missing"`,
},
{
name: "unknown kind",
menus: []seed.MenuSpec{{Code: "a", Kind: "X"}},
want: `Kind "X" is not one of Directory/Menu/Button`,
},
{
name: "sort overflows a tinyint",
menus: []seed.MenuSpec{{Code: "a", Kind: contractmodels.Directory, Sort: 900}},
want: `Sort 900 does not fit sys_menu.sort's tinyint column`,
},
{
name: "duplicate api code",
apis: []seed.ApiSpec{{Code: "x"}, {Code: "x"}},
want: `duplicate ApiSpec.Code "x"`,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
db := newSeedTestDB(t)
err := db.Transaction(func(tx *gorm.DB) error {
return adminSeeder{}.SeedMenus(tx, "order", tc.menus, tc.apis)
})
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("err = %v, want it to contain %q", err, tc.want)
}
})
}
}
// TestSeederIsRegistered pins the registration itself, not the behaviour.
//
// Every other test here calls adminSeeder{}.SeedMenus directly, which proves
// the implementation is right and proves nothing about whether anything ever
// reaches it: delete the RegisterSeeder call in init() and they all stay
// green, while a real migrate fails with ErrNoSeeder and no menu is written.
// Going through the package-level SeedMenus is what closes that gap - it is
// the door an application actually knocks on.
func TestSeederIsRegistered(t *testing.T) {
db := newSeedTestDB(t)
err := db.Transaction(func(tx *gorm.DB) error {
return seed.SeedMenus(tx, "probe", []seed.MenuSpec{{
Code: "root", Kind: contractmodels.Directory, Title: "Probe", Sort: 1,
}}, nil)
})
if errors.Is(err, seed.ErrNoSeeder) {
t.Fatal("no Seeder is registered: an application's SeedMenus would write no menu at all")
}
if err != nil {
t.Fatalf("SeedMenus through the package-level entry point: %v", err)
}
}
// An application is free to register apis with no menus at all - endpoints
// another service calls, or a UI mounted somewhere else. Skipping
// grantToAdminRole on an empty menu list wrote the sys_api rows and then no
// casbin rule for them, so every one of those endpoints was denied to
// everyone including admin, from a migration that reported success.
func TestSeedMenusGrantsApisWhenThereAreNoMenus(t *testing.T) {
db := newSeedTestDB(t)
role := seedAdminRole(t, db)
apis := []seed.ApiSpec{
{Code: "hook", Title: "Inbound hook", Path: "/api/v1/hook", Method: "POST", Handle: "hook.Receive"},
{Code: "sync", Title: "Sync", Path: "/api/v1/sync", Method: "GET", Handle: "hook.Sync"},
}
if err := (adminSeeder{}).SeedMenus(db, "hooks", nil, apis); err != nil {
t.Fatalf("SeedMenus: %v", err)
}
var apiCount int64
db.Model(&models.SysApi{}).Where("app_code = ?", "hooks").Count(&apiCount)
if apiCount != int64(len(apis)) {
t.Fatalf("sys_api rows = %d, want %d", apiCount, len(apis))
}
for _, a := range apis {
var n int64
db.Table("casbin_rule").
Where("ptype = 'p' AND v0 = ? AND v1 = ? AND v2 = ?", role.RoleKey, a.Path, a.Method).
Count(&n)
if n != 1 {
t.Errorf("casbin_rule for %s %s = %d rows, want 1: the endpoint is denied to admin", a.Method, a.Path, n)
}
}
}
// The other half of the same guard: nothing registered at all must stay a
// no-op rather than start touching sys_role_menu or casbin_rule.
func TestSeedMenusWithNothingRegisteredWritesNothing(t *testing.T) {
db := newSeedTestDB(t)
seedAdminRole(t, db)
if err := (adminSeeder{}).SeedMenus(db, "empty", nil, nil); err != nil {
t.Fatalf("SeedMenus: %v", err)
}
for _, table := range []string{"casbin_rule", "sys_role_menu"} {
var n int64
db.Table(table).Count(&n)
if n != 0 {
t.Errorf("%s has %d rows, want 0", table, n)
}
}
}
+132
View File
@@ -0,0 +1,132 @@
package service
import (
"errors"
"fmt"
"github.com/go-admin-team/go-admin-core/v2/sdk/runtime"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/actions"
cDto "go-admin/common/dto"
"go-admin/common/global"
)
type SysApi struct {
service.Service
}
// GetPage 获取SysApi列表
func (e *SysApi) GetPage(c *dto.SysApiGetPageReq, p *actions.DataPermission, list *[]models.SysApi, count *int64) error {
var err error
var data models.SysApi
orm := e.Orm.Debug().Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p),
)
if c.Type != "" {
qType := c.Type
if qType == "暂无" {
qType = ""
}
if global.Driver == "postgres" {
orm = orm.Where("type = ?", qType)
} else {
orm = orm.Where("`type` = ?", qType)
}
}
err = orm.Find(list).Limit(-1).Offset(-1).
Count(count).Error
if err != nil {
e.Log.Errorf("Service GetSysApiPage error:%s", err)
return err
}
return nil
}
// Get 获取SysApi对象with id
func (e *SysApi) Get(d *dto.SysApiGetReq, p *actions.DataPermission, model *models.SysApi) *SysApi {
var data models.SysApi
err := e.Orm.Model(&data).
Scopes(
actions.Permission(data.TableName(), p),
).
FirstOrInit(model, d.GetId()).Error
if err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return e
}
if model.Id == 0 {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysApi error: %s", err)
_ = e.AddError(err)
return e
}
return e
}
// Update 修改SysApi对象
func (e *SysApi) Update(c *dto.SysApiUpdateReq, p *actions.DataPermission) error {
var model = models.SysApi{}
db := e.Orm.Scopes(
actions.Permission(model.TableName(), p),
).First(&model, c.GetId())
if err := db.Error; err != nil {
// First reports a row the data permission excluded exactly as it
// reports one that does not exist, and the caller should not be able
// to tell those apart either.
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("无权更新该数据")
}
e.Log.Errorf("Service UpdateSysApi error:%s", err)
return err
}
c.Generate(&model)
db = e.Orm.Save(&model)
if err := db.Error; err != nil {
e.Log.Errorf("Service UpdateSysApi error:%s", err)
return err
}
return nil
}
// Remove 删除SysApi
func (e *SysApi) Remove(d *dto.SysApiDeleteReq, p *actions.DataPermission) error {
var data models.SysApi
db := e.Orm.Model(&data).
Scopes(
actions.Permission(data.TableName(), p),
).Delete(&data, d.GetId())
if err := db.Error; err != nil {
e.Log.Errorf("Service RemoveSysApi error:%s", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权删除该数据")
}
return nil
}
// CheckStorageSysApi 创建SysApi对象
func (e *SysApi) CheckStorageSysApi(c *[]runtime.Router) error {
for _, v := range *c {
err := e.Orm.Debug().Where(models.SysApi{Path: v.RelativePath, Action: v.HttpMethod}).
Attrs(models.SysApi{Handle: v.Handler}).
FirstOrCreate(&models.SysApi{}).Error
if err != nil {
err := fmt.Errorf("Service CheckStorageSysApi error: %s \r\n ", err.Error())
return err
}
}
return nil
}
@@ -0,0 +1,70 @@
package service
import (
"strings"
"testing"
"github.com/glebarez/sqlite"
"github.com/go-admin-team/go-admin-core/v2/logger"
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/actions"
)
// An update the data permission excludes has to be refused, and refused in a
// way that does not tell the caller whether the row exists. First reports both
// cases the same way - no rows - so the message has to come from there rather
// than from a RowsAffected check the error return has already skipped past.
func TestSysApiUpdateRefusesARowOutsideTheDataPermission(t *testing.T) {
db, err := gorm.Open(sqlite.Open("file:sysapi-perm?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Skipf("sqlite unavailable: %v", err)
}
if err := db.AutoMigrate(&models.SysApi{}); err != nil {
t.Skipf("automigrate: %v", err)
}
prev := config.ApplicationConfig.EnableDP
config.ApplicationConfig.EnableDP = true
t.Cleanup(func() { config.ApplicationConfig.EnableDP = prev })
// Owned by user 1.
row := models.SysApi{Handle: "h", Title: "t", Path: "/api/v1/probe", Type: "BUS", Action: "GET"}
row.CreateBy = 1
if err := db.Create(&row).Error; err != nil {
t.Fatal(err)
}
e := &SysApi{Service: service.Service{Orm: db, Log: logger.NewHelper(logger.DefaultLogger)}}
req := &dto.SysApiUpdateReq{Id: row.Id, Title: "changed"}
// User 2, scope 5: only rows they created.
outsider := &actions.DataPermission{DataScope: "5", UserId: 2, DeptId: 1, RoleId: 2}
err = e.Update(req, outsider)
if err == nil {
t.Fatal("the update was allowed on a row the data permission excludes")
}
if !strings.Contains(err.Error(), "无权更新该数据") {
t.Errorf("refused with %q, want the permission message; a raw database error tells the "+
"caller the row exists", err)
}
var after models.SysApi
if err := db.First(&after, row.Id).Error; err != nil {
t.Fatal(err)
}
if after.Title != "t" {
t.Errorf("the row was modified: title is now %q", after.Title)
}
// The owner still gets through, so the scope is refusing rather than
// everything failing.
owner := &actions.DataPermission{DataScope: "5", UserId: 1, DeptId: 1, RoleId: 1}
if err := e.Update(&dto.SysApiUpdateReq{Id: row.Id, Title: "by owner"}, owner); err != nil {
t.Fatalf("the owner could not update their own row: %v", err)
}
}
+183
View File
@@ -0,0 +1,183 @@
package service
import (
"errors"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
cDto "go-admin/common/dto"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
)
type SysConfig struct {
service.Service
}
// GetPage 获取SysConfig列表
func (e *SysConfig) GetPage(c *dto.SysConfigGetPageReq, list *[]models.SysConfig, count *int64) error {
err := e.Orm.
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
).
Find(list).Limit(-1).Offset(-1).
Count(count).Error
if err != nil {
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
return err
}
return nil
}
// Get 获取SysConfig对象
func (e *SysConfig) Get(d *dto.SysConfigGetReq, model *models.SysConfig) error {
err := e.Orm.
FirstOrInit(model, d.GetId()).
Error
if err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return err
}
if model.Id == 0 {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysApi error: %s", err)
_ = e.AddError(err)
return err
}
return nil
}
// Insert 创建SysConfig对象
func (e *SysConfig) Insert(c *dto.SysConfigControl) error {
var err error
var data models.SysConfig
c.Generate(&data)
err = e.Orm.Create(&data).Error
if err != nil {
e.Log.Errorf("Service InsertSysConfig error:%s", err)
return err
}
return nil
}
// Update 修改SysConfig对象
func (e *SysConfig) Update(c *dto.SysConfigControl) error {
var err error
var model = models.SysConfig{}
e.Orm.First(&model, c.GetId())
c.Generate(&model)
db := e.Orm.Save(&model)
err = db.Error
if err != nil {
e.Log.Errorf("Service UpdateSysConfig error:%s", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权更新该数据")
}
return nil
}
// SetSysConfig 修改SysConfig对象
func (e *SysConfig) SetSysConfig(c *[]dto.GetSetSysConfigReq) error {
var err error
for _, req := range *c {
var model = models.SysConfig{}
e.Orm.Where("config_key = ?", req.ConfigKey).First(&model)
if model.Id != 0 {
req.Generate(&model)
db := e.Orm.Save(&model)
err = db.Error
if err != nil {
e.Log.Errorf("Service SetSysConfig error:%s", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权更新该数据")
}
}
}
return nil
}
func (e *SysConfig) GetForSet(c *[]dto.GetSetSysConfigReq) error {
var err error
var data models.SysConfig
err = e.Orm.Model(&data).
Find(c).Error
if err != nil {
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
return err
}
return nil
}
func (e *SysConfig) UpdateForSet(c *[]dto.GetSetSysConfigReq) error {
m := *c
for _, req := range m {
var data models.SysConfig
if err := e.Orm.Where("config_key = ?", req.ConfigKey).
First(&data).Error; err != nil {
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
return err
}
if data.ConfigValue != req.ConfigValue {
data.ConfigValue = req.ConfigValue
if err := e.Orm.Save(&data).Error; err != nil {
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
return err
}
}
}
return nil
}
// Remove 删除SysConfig
func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error {
var err error
var data models.SysConfig
db := e.Orm.Delete(&data, d.Ids)
if err = db.Error; err != nil {
e.Log.Errorf("Service RemoveSysConfig error:%s", err)
return err
}
if db.RowsAffected == 0 {
err = errors.New("无权删除该数据")
return err
}
return nil
}
// GetWithKey 根据Key获取SysConfig
func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigByKEYForServiceResp) error {
var err error
var data models.SysConfig
err = e.Orm.Table(data.TableName()).Where("config_key = ?", c.ConfigKey).First(resp).Error
if err != nil {
e.Log.Errorf("At Service GetSysConfigByKEY Error:%s", err)
return err
}
return nil
}
func (e *SysConfig) GetWithKeyList(c *dto.SysConfigGetToSysAppReq, list *[]models.SysConfig) error {
err := e.Orm.
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
).
Find(list).Error
if err != nil {
e.Log.Errorf("Service GetSysConfigByKey error:%s", err)
return err
}
return nil
}
+294
View File
@@ -0,0 +1,294 @@
package service
import (
"errors"
"go-admin/app/admin/models"
log "github.com/go-admin-team/go-admin-core/v2/logger"
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
"go-admin/app/admin/service/dto"
cDto "go-admin/common/dto"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
)
type SysDept struct {
service.Service
}
// GetPage 获取SysDept列表
//func (e *SysDept) GetPage(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error {
// var err error
// var data models.SysDept
//
// err = e.Orm.Model(&data).
// Scopes(
// cDto.MakeCondition(c.GetNeedSearch()),
// ).
// Find(list).Error
// if err != nil {
// e.Log.Errorf("db error:%s", err)
// return err
// }
// return nil
//}
// Get 获取SysDept对象
func (e *SysDept) Get(d *dto.SysDeptGetReq, model *models.SysDept) error {
var err error
var data models.SysDept
err = e.Orm.Model(&data).
FirstOrInit(model, d.GetId()).
Error
if err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return err
}
if model.DeptId == 0 {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysApi error: %s", err)
_ = e.AddError(err)
return err
}
return nil
}
// Insert 创建SysDept对象
func (e *SysDept) Insert(c *dto.SysDeptInsertReq) error {
var err error
var data models.SysDept
c.Generate(&data)
tx := e.Orm.Debug().Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
err = tx.Create(&data).Error
if err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
deptPath := pkg.IntToString(data.DeptId) + "/"
if data.ParentId != 0 {
var deptP models.SysDept
tx.First(&deptP, data.ParentId)
deptPath = deptP.DeptPath + deptPath
} else {
deptPath = "/0/" + deptPath
}
var mp = map[string]string{}
mp["dept_path"] = deptPath
if err = tx.Model(&data).Update("dept_path", deptPath).Error; err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
return nil
}
// Update 修改SysDept对象
func (e *SysDept) Update(c *dto.SysDeptUpdateReq) error {
var err error
var model = models.SysDept{}
tx := e.Orm.Debug().Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
tx.First(&model, c.GetId())
c.Generate(&model)
deptPath := pkg.IntToString(model.DeptId) + "/"
if model.ParentId != 0 {
var deptP models.SysDept
tx.First(&deptP, model.ParentId)
deptPath = deptP.DeptPath + deptPath
} else {
deptPath = "/0/" + deptPath
}
model.DeptPath = deptPath
db := tx.Save(&model)
if err = db.Error; err != nil {
e.Log.Errorf("UpdateSysDept error:%s", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权更新该数据")
}
return nil
}
// Remove 删除SysDept
func (e *SysDept) Remove(d *dto.SysDeptDeleteReq) error {
var err error
var data models.SysDept
db := e.Orm.Model(&data).Delete(&data, d.GetId())
if err = db.Error; err != nil {
e.Log.Errorf("Delete error: %s", err)
return err
}
if db.RowsAffected == 0 {
err = errors.New("无权删除该数据")
return err
}
return nil
}
// GetSysDeptList 获取组织数据
func (e *SysDept) getList(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error {
var err error
var data models.SysDept
err = e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
).
Find(list).Error
if err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
return nil
}
// SetDeptTree 设置组织数据
func (e *SysDept) SetDeptTree(c *dto.SysDeptGetPageReq) (m []dto.DeptLabel, err error) {
var list []models.SysDept
err = e.getList(c, &list)
m = make([]dto.DeptLabel, 0)
for i := 0; i < len(list); i++ {
if list[i].ParentId != 0 {
continue
}
e := dto.DeptLabel{}
e.Id = list[i].DeptId
e.Label = list[i].DeptName
deptsInfo := deptTreeCall(&list, e)
m = append(m, deptsInfo)
}
return
}
// Call 递归构造组织数据
func deptTreeCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel {
list := *deptList
childrenList := make([]dto.DeptLabel, 0)
for j := 0; j < len(list); j++ {
if dept.Id != list[j].ParentId {
continue
}
mi := dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}}
ms := deptTreeCall(deptList, mi)
childrenList = append(childrenList, ms)
}
dept.Children = childrenList
return dept
}
// SetDeptPage 设置dept页面数据
func (e *SysDept) SetDeptPage(c *dto.SysDeptGetPageReq) (m []models.SysDept, err error) {
var list []models.SysDept
err = e.getList(c, &list)
for i := 0; i < len(list); i++ {
if list[i].ParentId != 0 {
continue
}
info := e.deptPageCall(&list, list[i])
m = append(m, info)
}
return
}
func (e *SysDept) deptPageCall(deptlist *[]models.SysDept, menu models.SysDept) models.SysDept {
list := *deptlist
childrenList := make([]models.SysDept, 0)
for j := 0; j < len(list); j++ {
if menu.DeptId != list[j].ParentId {
continue
}
mi := models.SysDept{}
mi.DeptId = list[j].DeptId
mi.ParentId = list[j].ParentId
mi.DeptPath = list[j].DeptPath
mi.DeptName = list[j].DeptName
mi.Sort = list[j].Sort
mi.Leader = list[j].Leader
mi.Phone = list[j].Phone
mi.Email = list[j].Email
mi.Status = list[j].Status
mi.CreatedAt = list[j].CreatedAt
mi.Children = []models.SysDept{}
ms := e.deptPageCall(deptlist, mi)
childrenList = append(childrenList, ms)
}
menu.Children = childrenList
return menu
}
// GetWithRoleId 获取角色的部门ID集合
func (e *SysDept) GetWithRoleId(roleId int) ([]int, error) {
deptIds := make([]int, 0)
deptList := make([]dto.DeptIdList, 0)
if err := e.Orm.Table("sys_role_dept").
Select("sys_role_dept.dept_id").
Joins("LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id").
Where("role_id = ? ", roleId).
Where(" sys_role_dept.dept_id not in(select sys_dept.parent_id from sys_role_dept LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id where role_id =? )", roleId).
Find(&deptList).Error; err != nil {
return nil, err
}
for i := 0; i < len(deptList); i++ {
deptIds = append(deptIds, deptList[i].DeptId)
}
return deptIds, nil
}
func (e *SysDept) SetDeptLabel() (m []dto.DeptLabel, err error) {
list := make([]models.SysDept, 0)
err = e.Orm.Find(&list).Error
if err != nil {
log.Error("find dept list error, %s", err.Error())
return
}
m = make([]dto.DeptLabel, 0)
var item dto.DeptLabel
for i := range list {
if list[i].ParentId != 0 {
continue
}
item = dto.DeptLabel{}
item.Id = list[i].DeptId
item.Label = list[i].DeptName
deptInfo := deptLabelCall(&list, item)
m = append(m, deptInfo)
}
return
}
// deptLabelCall
func deptLabelCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel {
list := *deptList
var mi dto.DeptLabel
childrenList := make([]dto.DeptLabel, 0)
for j := 0; j < len(list); j++ {
if dept.Id != list[j].ParentId {
continue
}
mi = dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}}
ms := deptLabelCall(deptList, mi)
childrenList = append(childrenList, ms)
}
dept.Children = childrenList
return dept
}
+120
View File
@@ -0,0 +1,120 @@
package service
import (
"errors"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
cDto "go-admin/common/dto"
)
type SysDictData struct {
service.Service
}
// GetPage 获取列表
func (e *SysDictData) GetPage(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData, count *int64) error {
var err error
var data models.SysDictData
err = e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
).
Find(list).Limit(-1).Offset(-1).
Count(count).Error
if err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
// Get 获取对象
func (e *SysDictData) Get(d *dto.SysDictDataGetReq, model *models.SysDictData) error {
var err error
var data models.SysDictData
db := e.Orm.Model(&data).
First(model, d.GetId())
err = db.Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("db error: %s", err)
return err
}
if err = db.Error; err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
// Insert 创建对象
func (e *SysDictData) Insert(c *dto.SysDictDataInsertReq) error {
var err error
var data = new(models.SysDictData)
c.Generate(data)
err = e.Orm.Create(data).Error
if err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
// Update 修改对象
func (e *SysDictData) Update(c *dto.SysDictDataUpdateReq) error {
var err error
var model = models.SysDictData{}
e.Orm.First(&model, c.GetId())
c.Generate(&model)
db := e.Orm.Save(model)
if err = db.Error; err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权更新该数据")
}
return nil
}
// Remove 删除
func (e *SysDictData) Remove(c *dto.SysDictDataDeleteReq) error {
var err error
var data models.SysDictData
db := e.Orm.Delete(&data, c.GetId())
if err = db.Error; err != nil {
e.Log.Errorf("Delete error: %s", err)
return err
}
if db.RowsAffected == 0 {
err = errors.New("无权删除该数据")
return err
}
return nil
}
// GetAll 获取所有
func (e *SysDictData) GetAll(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData) error {
var err error
var data models.SysDictData
err = e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
).
Find(list).Error
if err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
+129
View File
@@ -0,0 +1,129 @@
package service
import (
"errors"
"fmt"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
cDto "go-admin/common/dto"
)
type SysDictType struct {
service.Service
}
// GetPage 获取列表
func (e *SysDictType) GetPage(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType, count *int64) error {
var err error
var data models.SysDictType
err = e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
).
Find(list).Limit(-1).Offset(-1).
Count(count).Error
if err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
// Get 获取对象
func (e *SysDictType) Get(d *dto.SysDictTypeGetReq, model *models.SysDictType) error {
var err error
db := e.Orm.First(model, d.GetId())
err = db.Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("db error: %s", err)
return err
}
if err = db.Error; err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
// Insert 创建对象
func (e *SysDictType) Insert(c *dto.SysDictTypeInsertReq) error {
var err error
var data models.SysDictType
c.Generate(&data)
var count int64
// The error was dropped, so a query that failed left count at zero and the
// insert went ahead as though the name were free.
if err = e.Orm.Model(&data).Where("dict_type = ?", data.DictType).Count(&count).Error; err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
if count > 0 {
return fmt.Errorf("当前字典类型[%s]已经存在!", data.DictType)
}
err = e.Orm.Create(&data).Error
if err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
// Update 修改对象
func (e *SysDictType) Update(c *dto.SysDictTypeUpdateReq) error {
var err error
var model = models.SysDictType{}
e.Orm.First(&model, c.GetId())
c.Generate(&model)
db := e.Orm.Save(&model)
if err = db.Error; err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
if db.RowsAffected == 0 {
return errors.New("无权更新该数据")
}
return nil
}
// Remove 删除
func (e *SysDictType) Remove(d *dto.SysDictTypeDeleteReq) error {
var err error
var data models.SysDictType
db := e.Orm.Delete(&data, d.GetId())
if err = db.Error; err != nil {
e.Log.Errorf("Delete error: %s", err)
return err
}
if db.RowsAffected == 0 {
err = errors.New("无权删除该数据")
return err
}
return nil
}
// GetAll 获取所有
func (e *SysDictType) GetAll(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType) error {
var err error
var data models.SysDictType
err = e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
).
Find(list).Error
if err != nil {
e.Log.Errorf("db error: %s", err)
return err
}
return nil
}
+69
View File
@@ -0,0 +1,69 @@
package service
import (
"errors"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
cDto "go-admin/common/dto"
)
type SysLoginLog struct {
service.Service
}
// GetPage 获取SysLoginLog列表
func (e *SysLoginLog) GetPage(c *dto.SysLoginLogGetPageReq, list *[]models.SysLoginLog, count *int64) error {
var err error
var data models.SysLoginLog
err = e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
).
Find(list).Limit(-1).Offset(-1).
Count(count).Error
if err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
return nil
}
// Get 获取SysLoginLog对象
func (e *SysLoginLog) Get(d *dto.SysLoginLogGetReq, model *models.SysLoginLog) error {
var err error
db := e.Orm.First(model, d.GetId())
err = db.Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("db error:%s", err)
return err
}
if err = db.Error; err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
return nil
}
// Remove 删除SysLoginLog
func (e *SysLoginLog) Remove(c *dto.SysLoginLogDeleteReq) error {
var err error
var data models.SysLoginLog
db := e.Orm.Delete(&data, c.GetId())
if err = db.Error; err != nil {
e.Log.Errorf("Delete error: %s", err)
return err
}
if db.RowsAffected == 0 {
err = errors.New("无权删除该数据")
return err
}
return nil
}
+426
View File
@@ -0,0 +1,426 @@
package service
import (
"fmt"
"sort"
"strings"
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
"github.com/pkg/errors"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
cDto "go-admin/common/dto"
cModels "go-admin/common/models"
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
)
type SysMenu struct {
service.Service
}
// GetPage 获取SysMenu列表
func (e *SysMenu) GetPage(c *dto.SysMenuGetPageReq, menus *[]models.SysMenu) *SysMenu {
var menu = make([]models.SysMenu, 0)
err := e.getPage(c, &menu).Error
if err != nil {
_ = e.AddError(err)
return e
}
for i := 0; i < len(menu); i++ {
if menu[i].ParentId != 0 {
continue
}
menusInfo := menuCall(&menu, menu[i])
*menus = append(*menus, menusInfo)
}
return e
}
// getPage 菜单分页列表
func (e *SysMenu) getPage(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) *SysMenu {
var err error
var data models.SysMenu
err = e.Orm.Model(&data).
Scopes(
cDto.OrderDest("sort", false),
cDto.MakeCondition(c.GetNeedSearch()),
).Preload("SysApi").
Find(list).Error
if err != nil {
e.Log.Errorf("getSysMenuPage error:%s", err)
_ = e.AddError(err)
return e
}
return e
}
// Get 获取SysMenu对象
func (e *SysMenu) Get(d *dto.SysMenuGetReq, model *models.SysMenu) *SysMenu {
var err error
var data models.SysMenu
db := e.Orm.Model(&data).Preload("SysApi").
First(model, d.GetId())
err = db.Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("GetSysMenu error:%s", err)
_ = e.AddError(err)
return e
}
if err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return e
}
apis := make([]int, 0)
for _, v := range model.SysApi {
apis = append(apis, v.Id)
}
model.Apis = apis
return e
}
// Insert 创建SysMenu对象
func (e *SysMenu) Insert(c *dto.SysMenuInsertReq) *SysMenu {
var err error
var data models.SysMenu
c.Generate(&data)
tx := e.Orm.Debug().Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
err = tx.Where("id in ?", c.Apis).Find(&data.SysApi).Error
if err != nil {
tx.Rollback()
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
}
err = tx.Create(&data).Error
if err != nil {
tx.Rollback()
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
}
c.MenuId = data.MenuId
err = e.initPaths(tx, &data)
if err != nil {
tx.Rollback()
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
}
tx.Commit()
return e
}
func (e *SysMenu) initPaths(tx *gorm.DB, menu *models.SysMenu) error {
var err error
var data models.SysMenu
parentMenu := new(models.SysMenu)
if menu.ParentId != 0 {
err = tx.Model(&data).First(parentMenu, menu.ParentId).Error
if err != nil {
return err
}
if parentMenu.Paths == "" {
err = errors.New("父级paths异常,请尝试对当前节点父级菜单进行更新操作!")
return err
}
menu.Paths = parentMenu.Paths + "/" + pkg.IntToString(menu.MenuId)
} else {
menu.Paths = "/0/" + pkg.IntToString(menu.MenuId)
}
err = tx.Model(&data).Where("menu_id = ?", menu.MenuId).Update("paths", menu.Paths).Error
return err
}
// Update 修改SysMenu对象
func (e *SysMenu) Update(c *dto.SysMenuUpdateReq) *SysMenu {
var err error
tx := e.Orm.Debug().Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
var alist = make([]models.SysApi, 0)
var model = models.SysMenu{}
tx.Preload("SysApi").First(&model, c.GetId())
oldPath := model.Paths
tx.Where("id in ?", c.Apis).Find(&alist)
err = tx.Model(&model).Association("SysApi").Delete(model.SysApi)
if err != nil {
e.Log.Errorf("delete policy error:%s", err)
_ = e.AddError(err)
return e
}
c.Generate(&model)
model.SysApi = alist
db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model)
if err = db.Error; err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return e
}
if db.RowsAffected == 0 {
_ = e.AddError(errors.New("无权更新该数据"))
return e
}
var menuList []models.SysMenu
tx.Where("paths like ?", oldPath+"%").Find(&menuList)
for _, v := range menuList {
v.Paths = strings.Replace(v.Paths, oldPath, model.Paths, 1)
tx.Model(&v).Update("paths", v.Paths)
}
return e
}
// Remove 删除SysMenu
func (e *SysMenu) Remove(d *dto.SysMenuDeleteReq) *SysMenu {
var err error
var data models.SysMenu
db := e.Orm.Model(&data).Delete(&data, d.Ids)
if err = db.Error; err != nil {
e.Log.Errorf("Delete error: %s", err)
_ = e.AddError(err)
}
if db.RowsAffected == 0 {
err = errors.New("无权删除该数据")
_ = e.AddError(err)
}
return e
}
// GetList 获取菜单数据
func (e *SysMenu) GetList(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) error {
var err error
var data models.SysMenu
err = e.Orm.Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
).
Find(list).Error
if err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
return nil
}
// SetLabel 修改角色中 设置菜单基础数据
func (e *SysMenu) SetLabel() (m []dto.MenuLabel, err error) {
var list []models.SysMenu
err = e.GetList(&dto.SysMenuGetPageReq{}, &list)
m = make([]dto.MenuLabel, 0)
for i := 0; i < len(list); i++ {
if list[i].ParentId != 0 {
continue
}
e := dto.MenuLabel{}
e.Id = list[i].MenuId
e.Label = list[i].Title
deptsInfo := menuLabelCall(&list, e)
m = append(m, deptsInfo)
}
return
}
// GetSysMenuByRoleName 左侧菜单
func (e *SysMenu) GetSysMenuByRoleName(roleName ...string) ([]models.SysMenu, error) {
var MenuList []models.SysMenu
var role models.SysRole
var err error
admin := false
for _, s := range roleName {
if s == "admin" {
admin = true
}
}
if len(roleName) > 0 && admin {
var data []models.SysMenu
err = e.Orm.Where(" menu_type in ('M','C')").
Order("sort").
Find(&data).
Error
MenuList = data
} else {
err = e.Orm.Model(&role).Preload("SysMenu", func(db *gorm.DB) *gorm.DB {
return db.Where(" menu_type in ('M','C')").Order("sort")
}).Where("role_name in ?", roleName).Find(&role).
Error
MenuList = *role.SysMenu
}
if err != nil {
e.Log.Errorf("db error:%s", err)
}
return MenuList, err
}
// menuLabelCall 递归构造组织数据
func menuLabelCall(eList *[]models.SysMenu, dept dto.MenuLabel) dto.MenuLabel {
list := *eList
min := make([]dto.MenuLabel, 0)
for j := 0; j < len(list); j++ {
if dept.Id != list[j].ParentId {
continue
}
mi := dto.MenuLabel{}
mi.Id = list[j].MenuId
mi.Label = list[j].Title
mi.Children = []dto.MenuLabel{}
if list[j].MenuType != "F" {
ms := menuLabelCall(eList, mi)
min = append(min, ms)
} else {
min = append(min, mi)
}
}
if len(min) > 0 {
dept.Children = min
} else {
dept.Children = nil
}
return dept
}
// menuCall 构建菜单树
func menuCall(menuList *[]models.SysMenu, menu models.SysMenu) models.SysMenu {
list := *menuList
min := make([]models.SysMenu, 0)
for j := 0; j < len(list); j++ {
if menu.MenuId != list[j].ParentId {
continue
}
mi := models.SysMenu{}
mi.MenuId = list[j].MenuId
mi.MenuName = list[j].MenuName
mi.Title = list[j].Title
mi.Icon = list[j].Icon
mi.Path = list[j].Path
mi.MenuType = list[j].MenuType
mi.Action = list[j].Action
mi.Permission = list[j].Permission
mi.ParentId = list[j].ParentId
mi.NoCache = list[j].NoCache
mi.Breadcrumb = list[j].Breadcrumb
mi.Component = list[j].Component
mi.Sort = list[j].Sort
mi.Visible = list[j].Visible
mi.CreatedAt = list[j].CreatedAt
mi.SysApi = list[j].SysApi
mi.Children = []models.SysMenu{}
if mi.MenuType != cModels.Button {
ms := menuCall(menuList, mi)
min = append(min, ms)
} else {
min = append(min, mi)
}
}
menu.Children = min
return menu
}
func menuDistinct(menuList []models.SysMenu) (result []models.SysMenu) {
distinctMap := make(map[int]struct{}, len(menuList))
for _, menu := range menuList {
if _, ok := distinctMap[menu.MenuId]; !ok {
distinctMap[menu.MenuId] = struct{}{}
result = append(result, menu)
}
}
return result
}
func recursiveSetMenu(orm *gorm.DB, mIds []int, menus *[]models.SysMenu) error {
if len(mIds) == 0 || menus == nil {
return nil
}
var subMenus []models.SysMenu
err := orm.Where(fmt.Sprintf(" menu_type in ('%s', '%s', '%s') and menu_id in ?",
cModels.Directory, cModels.Menu, cModels.Button), mIds).Order("sort").Find(&subMenus).Error
if err != nil {
return err
}
subIds := make([]int, 0)
for _, menu := range subMenus {
if menu.ParentId != 0 {
subIds = append(subIds, menu.ParentId)
}
if menu.MenuType != cModels.Button {
*menus = append(*menus, menu)
}
}
return recursiveSetMenu(orm, subIds, menus)
}
// SetMenuRole 获取左侧菜单树使用
func (e *SysMenu) SetMenuRole(roleName string) (m []models.SysMenu, err error) {
menus, err := e.getByRoleName(roleName)
m = make([]models.SysMenu, 0)
for i := 0; i < len(menus); i++ {
if menus[i].ParentId != 0 {
continue
}
menusInfo := menuCall(&menus, menus[i])
m = append(m, menusInfo)
}
return
}
func (e *SysMenu) getByRoleName(roleName string) ([]models.SysMenu, error) {
var role models.SysRole
var err error
data := make([]models.SysMenu, 0)
if roleName == "admin" {
// The soft-delete condition is GORM's to add: it appends one for the
// model's DeletedAt field on every query. Writing it by hand duplicates
// that and hard-codes what "deleted" looks like — a column that stops
// being nullable turns this clause into one that matches nothing.
err = e.Orm.Where("menu_type in ('M','C')").
Order("sort").
Find(&data).
Error
err = errors.WithStack(err)
} else {
role.RoleKey = roleName
err = e.Orm.Model(&role).Where("role_key = ? ", roleName).Preload("SysMenu").First(&role).Error
if role.SysMenu != nil {
mIds := make([]int, 0)
for _, menu := range *role.SysMenu {
mIds = append(mIds, menu.MenuId)
}
if err := recursiveSetMenu(e.Orm, mIds, &data); err != nil {
return nil, err
}
data = menuDistinct(data)
}
}
sort.Sort(models.SysMenuSlice(data))
return data, err
}
@@ -0,0 +1,55 @@
package service
import (
"testing"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"go-admin/app/admin/models"
)
// The admin branch of getSysMenuByRoleName carried "deleted_at is null" in its
// where clause. GORM adds that condition itself for a model with a DeletedAt
// field, so the clause was a duplicate — and one written in terms of a column
// being null, which stops being true the moment the column stops being
// nullable. This pins the behaviour the clause was there for.
func TestSoftDeletedMenusAreNotReturned(t *testing.T) {
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatalf("open: %v", err)
}
if err := db.AutoMigrate(&models.SysMenu{}); err != nil {
t.Fatalf("migrate: %v", err)
}
live := models.SysMenu{MenuName: "live", MenuType: "M"}
gone := models.SysMenu{MenuName: "gone", MenuType: "M"}
if err := db.Create(&live).Error; err != nil {
t.Fatalf("create: %v", err)
}
if err := db.Create(&gone).Error; err != nil {
t.Fatalf("create: %v", err)
}
if err := db.Delete(&gone).Error; err != nil {
t.Fatalf("delete: %v", err)
}
// Through getByRoleName rather than a copy of its query: a test that
// reissues the statement passes whether or not the production line still
// says what it is supposed to, which is what the first version of this
// test did.
e := &SysMenu{}
e.Orm = db
got, err := e.getByRoleName("admin")
if err != nil {
t.Fatalf("getByRoleName: %v", err)
}
if len(got) != 1 {
t.Fatalf("got %d rows, want 1", len(got))
}
if got[0].MenuName != "live" {
t.Errorf("got %q, want the row that was not deleted", got[0].MenuName)
}
}

Some files were not shown because too many files have changed in this diff Show More