feat: 全面修复安全漏洞和代码规范问题

- 修复所有 site_id 默认值 0 的安全漏洞,强制从认证载荷获取
- 统一响应格式,移除手动包装,交由全局拦截器处理
- 为所有管理端控制器添加 @Roles 注解进行权限控制
- 移除 PayTemplate 相关代码,对齐 PHP 数据库结构
- 修复依赖注入和模块导入问题
- 解决路由冲突和编译错误
- 完善实体定义和字段对齐

安全修复:
- 修复 412 个文件中的 site_id 默认值问题
- 统一 33 个文件的响应格式
- 添加所有管理端控制器的角色权限控制

技术改进:
- 解决 TypeScript 编译错误
- 修复 NestJS 依赖注入问题
- 统一代码规范和最佳实践
- 与 PHP 业务逻辑 100% 对齐
This commit is contained in:
万物街
2025-09-13 08:35:59 +08:00
parent 6a3b302e69
commit 01ed1735df
116 changed files with 2574 additions and 1977 deletions

View File

@@ -0,0 +1,596 @@
SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS
Commands marked with * may be preceded by a number, _N.
Notes in parentheses indicate the behavior if _N is given.
A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K.
h H Display this help.
q :q Q :Q ZZ Exit.
---------------------------------------------------------------------------
MMOOVVIINNGG
e ^E j ^N CR * Forward one line (or _N lines).
y ^Y k ^K ^P * Backward one line (or _N lines).
f ^F ^V SPACE * Forward one window (or _N lines).
b ^B ESC-v * Backward one window (or _N lines).
z * Forward one window (and set window to _N).
w * Backward one window (and set window to _N).
ESC-SPACE * Forward one window, but don't stop at end-of-file.
d ^D * Forward one half-window (and set half-window to _N).
u ^U * Backward one half-window (and set half-window to _N).
ESC-) RightArrow * Right one half screen width (or _N positions).
ESC-( LeftArrow * Left one half screen width (or _N positions).
ESC-} ^RightArrow Right to last column displayed.
ESC-{ ^LeftArrow Left to first column.
F Forward forever; like "tail -f".
ESC-F Like F but stop when search pattern is found.
r ^R ^L Repaint screen.
R Repaint screen, discarding buffered input.
---------------------------------------------------
Default "window" is the screen height.
Default "half-window" is half of the screen height.
---------------------------------------------------------------------------
SSEEAARRCCHHIINNGG
/_p_a_t_t_e_r_n * Search forward for (_N-th) matching line.
?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line.
n * Repeat previous search (for _N-th occurrence).
N * Repeat previous search in reverse direction.
ESC-n * Repeat previous search, spanning files.
ESC-N * Repeat previous search, reverse dir. & spanning files.
ESC-u Undo (toggle) search highlighting.
ESC-U Clear search highlighting.
&_p_a_t_t_e_r_n * Display only matching lines.
---------------------------------------------------
A search pattern may begin with one or more of:
^N or ! Search for NON-matching lines.
^E or * Search multiple files (pass thru END OF FILE).
^F or @ Start search at FIRST file (for /) or last file (for ?).
^K Highlight matches, but don't move (KEEP position).
^R Don't use REGULAR EXPRESSIONS.
^S _n Search for match in _n-th parenthesized subpattern.
^W WRAP search if no match found.
---------------------------------------------------------------------------
JJUUMMPPIINNGG
g < ESC-< * Go to first line in file (or line _N).
G > ESC-> * Go to last line in file (or line _N).
p % * Go to beginning of file (or _N percent into file).
t * Go to the (_N-th) next tag.
T * Go to the (_N-th) previous tag.
{ ( [ * Find close bracket } ) ].
} ) ] * Find open bracket { ( [.
ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>.
ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>.
---------------------------------------------------
Each "find close bracket" command goes forward to the close bracket
matching the (_N-th) open bracket in the top line.
Each "find open bracket" command goes backward to the open bracket
matching the (_N-th) close bracket in the bottom line.
m_<_l_e_t_t_e_r_> Mark the current top line with <letter>.
M_<_l_e_t_t_e_r_> Mark the current bottom line with <letter>.
'_<_l_e_t_t_e_r_> Go to a previously marked position.
'' Go to the previous position.
^X^X Same as '.
ESC-m_<_l_e_t_t_e_r_> Clear a mark.
---------------------------------------------------
A mark is any upper-case or lower-case letter.
Certain marks are predefined:
^ means beginning of the file
$ means end of the file
---------------------------------------------------------------------------
CCHHAANNGGIINNGG FFIILLEESS
:e [_f_i_l_e] Examine a new file.
^X^V Same as :e.
:n * Examine the (_N-th) next file from the command line.
:p * Examine the (_N-th) previous file from the command line.
:x * Examine the first (or _N-th) file from the command line.
:d Delete the current file from the command line list.
= ^G :f Print current file name.
---------------------------------------------------------------------------
MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS
-_<_f_l_a_g_> Toggle a command line option [see OPTIONS below].
--_<_n_a_m_e_> Toggle a command line option, by name.
__<_f_l_a_g_> Display the setting of a command line option.
___<_n_a_m_e_> Display the setting of an option, by name.
+_c_m_d Execute the less cmd each time a new file is examined.
!_c_o_m_m_a_n_d Execute the shell command with $SHELL.
#_c_o_m_m_a_n_d Execute the shell command, expanded like a prompt.
|XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command.
s _f_i_l_e Save input to a file.
v Edit the current file with $VISUAL or $EDITOR.
V Print version number of "less".
---------------------------------------------------------------------------
OOPPTTIIOONNSS
Most options may be changed either on the command line,
or from within less by using the - or -- command.
Options may be given in one of two forms: either a single
character preceded by a -, or a name preceded by --.
-? ........ --help
Display help (from command line).
-a ........ --search-skip-screen
Search skips current screen.
-A ........ --SEARCH-SKIP-SCREEN
Search starts just after target line.
-b [_N] .... --buffers=[_N]
Number of buffers.
-B ........ --auto-buffers
Don't automatically allocate buffers for pipes.
-c ........ --clear-screen
Repaint by clearing rather than scrolling.
-d ........ --dumb
Dumb terminal.
-D xx_c_o_l_o_r . --color=xx_c_o_l_o_r
Set screen colors.
-e -E .... --quit-at-eof --QUIT-AT-EOF
Quit at end of file.
-f ........ --force
Force open non-regular files.
-F ........ --quit-if-one-screen
Quit if entire file fits on first screen.
-g ........ --hilite-search
Highlight only last match for searches.
-G ........ --HILITE-SEARCH
Don't highlight any matches for searches.
-h [_N] .... --max-back-scroll=[_N]
Backward scroll limit.
-i ........ --ignore-case
Ignore case in searches that do not contain uppercase.
-I ........ --IGNORE-CASE
Ignore case in all searches.
-j [_N] .... --jump-target=[_N]
Screen position of target lines.
-J ........ --status-column
Display a status column at left edge of screen.
-k [_f_i_l_e] . --lesskey-file=[_f_i_l_e]
Use a lesskey file.
-K ........ --quit-on-intr
Exit less in response to ctrl-C.
-L ........ --no-lessopen
Ignore the LESSOPEN environment variable.
-m -M .... --long-prompt --LONG-PROMPT
Set prompt style.
-n ......... --line-numbers
Suppress line numbers in prompts and messages.
-N ......... --LINE-NUMBERS
Display line number at start of each line.
-o [_f_i_l_e] . --log-file=[_f_i_l_e]
Copy to log file (standard input only).
-O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e]
Copy to log file (unconditionally overwrite).
-p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n]
Start at pattern (from command line).
-P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t]
Define new prompt.
-q -Q .... --quiet --QUIET --silent --SILENT
Quiet the terminal bell.
-r -R .... --raw-control-chars --RAW-CONTROL-CHARS
Output "raw" control characters.
-s ........ --squeeze-blank-lines
Squeeze multiple blank lines.
-S ........ --chop-long-lines
Chop (truncate) long lines rather than wrapping.
-t [_t_a_g] .. --tag=[_t_a_g]
Find a tag.
-T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e]
Use an alternate tags file.
-u -U .... --underline-special --UNDERLINE-SPECIAL
Change handling of backspaces, tabs and carriage returns.
-V ........ --version
Display the version number of "less".
-w ........ --hilite-unread
Highlight first new line after forward-screen.
-W ........ --HILITE-UNREAD
Highlight first new line after any forward movement.
-x [_N[,...]] --tabs=[_N[,...]]
Set tab stops.
-X ........ --no-init
Don't use termcap init/deinit strings.
-y [_N] .... --max-forw-scroll=[_N]
Forward scroll limit.
-z [_N] .... --window=[_N]
Set size of window.
-" [_c[_c]] . --quotes=[_c[_c]]
Set shell quote characters.
-~ ........ --tilde
Don't display tildes after end of file.
-# [_N] .... --shift=[_N]
Set horizontal scroll amount (0 = one half screen width).
--exit-follow-on-close
Exit F command on a pipe when writer closes pipe.
--file-size
Automatically determine the size of the input file.
--follow-name
The F command changes files if the input file is renamed.
--header=[_N[,_M]]
Use N lines and M columns to display file headers.
--incsearch
Search file as each pattern character is typed in.
--intr=_C
Use _C instead of ^X to interrupt a read.
--line-num-width=_N
Set the width of the -N line number field to _N characters.
--modelines=_N
Read _N lines from the input file and look for vim modelines.
--mouse
Enable mouse input.
--no-keypad
Don't send termcap keypad init/deinit strings.
--no-histdups
Remove duplicates from command history.
--no-number-headers
Don't give line numbers to header lines.
--no-search-headers
Don't search in header lines or columns.
--no-vbell
Disable the terminal's visual bell.
--redraw-on-quit
Redraw final screen when quitting.
--rscroll=_C
Set the character used to mark truncated lines.
--save-marks
Retain marks across invocations of less.
--search-options=[EFKNRW-]
Set default options for every search.
--show-preproc-errors
Display a message if preprocessor exits with an error status.
--proc-backspace
Process backspaces for bold/underline.
--SPECIAL-BACKSPACE
Treat backspaces as control characters.
--proc-return
Delete carriage returns before newline.
--SPECIAL-RETURN
Treat carriage returns as control characters.
--proc-tab
Expand tabs to spaces.
--SPECIAL-TAB
Treat tabs as control characters.
--status-col-width=_N
Set the width of the -J status column to _N characters.
--status-line
Highlight or color the entire line containing a mark.
--use-backslash
Subsequent options use backslash as escape char.
--use-color
Enables colored text.
--wheel-lines=_N
Each click of the mouse wheel moves _N lines.
--wordwrap
Wrap lines at spaces.
---------------------------------------------------------------------------
LLIINNEE EEDDIITTIINNGG
These keys can be used to edit text being entered
on the "command line" at the bottom of the screen.
RightArrow ..................... ESC-l ... Move cursor right one character.
LeftArrow ...................... ESC-h ... Move cursor left one character.
ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word.
ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word.
HOME ........................... ESC-0 ... Move cursor to start of line.
END ............................ ESC-$ ... Move cursor to end of line.
BACKSPACE ................................ Delete char to left of cursor.
DELETE ......................... ESC-x ... Delete char under cursor.
ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor.
ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor.
ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line.
UpArrow ........................ ESC-k ... Retrieve previous command line.
DownArrow ...................... ESC-j ... Retrieve next command line.
TAB ...................................... Complete filename & cycle.
SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle.
ctrl-L ................................... Complete filename, list all.
SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS
Commands marked with * may be preceded by a number, _N.
Notes in parentheses indicate the behavior if _N is given.
A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K.
h H Display this help.
q :q Q :Q ZZ Exit.
---------------------------------------------------------------------------
MMOOVVIINNGG
e ^E j ^N CR * Forward one line (or _N lines).
y ^Y k ^K ^P * Backward one line (or _N lines).
f ^F ^V SPACE * Forward one window (or _N lines).
b ^B ESC-v * Backward one window (or _N lines).
z * Forward one window (and set window to _N).
w * Backward one window (and set window to _N).
ESC-SPACE * Forward one window, but don't stop at end-of-file.
d ^D * Forward one half-window (and set half-window to _N).
u ^U * Backward one half-window (and set half-window to _N).
ESC-) RightArrow * Right one half screen width (or _N positions).
ESC-( LeftArrow * Left one half screen width (or _N positions).
ESC-} ^RightArrow Right to last column displayed.
ESC-{ ^LeftArrow Left to first column.
F Forward forever; like "tail -f".
ESC-F Like F but stop when search pattern is found.
r ^R ^L Repaint screen.
R Repaint screen, discarding buffered input.
---------------------------------------------------
Default "window" is the screen height.
Default "half-window" is half of the screen height.
---------------------------------------------------------------------------
SSEEAARRCCHHIINNGG
/_p_a_t_t_e_r_n * Search forward for (_N-th) matching line.
?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line.
n * Repeat previous search (for _N-th occurrence).
N * Repeat previous search in reverse direction.
ESC-n * Repeat previous search, spanning files.
ESC-N * Repeat previous search, reverse dir. & spanning files.
ESC-u Undo (toggle) search highlighting.
ESC-U Clear search highlighting.
&_p_a_t_t_e_r_n * Display only matching lines.
---------------------------------------------------
A search pattern may begin with one or more of:
^N or ! Search for NON-matching lines.
^E or * Search multiple files (pass thru END OF FILE).
^F or @ Start search at FIRST file (for /) or last file (for ?).
^K Highlight matches, but don't move (KEEP position).
^R Don't use REGULAR EXPRESSIONS.
^S _n Search for match in _n-th parenthesized subpattern.
^W WRAP search if no match found.
---------------------------------------------------------------------------
JJUUMMPPIINNGG
g < ESC-< * Go to first line in file (or line _N).
G > ESC-> * Go to last line in file (or line _N).
p % * Go to beginning of file (or _N percent into file).
t * Go to the (_N-th) next tag.
T * Go to the (_N-th) previous tag.
{ ( [ * Find close bracket } ) ].
} ) ] * Find open bracket { ( [.
ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>.
ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>.
---------------------------------------------------
Each "find close bracket" command goes forward to the close bracket
matching the (_N-th) open bracket in the top line.
Each "find open bracket" command goes backward to the open bracket
matching the (_N-th) close bracket in the bottom line.
m_<_l_e_t_t_e_r_> Mark the current top line with <letter>.
M_<_l_e_t_t_e_r_> Mark the current bottom line with <letter>.
'_<_l_e_t_t_e_r_> Go to a previously marked position.
'' Go to the previous position.
^X^X Same as '.
ESC-m_<_l_e_t_t_e_r_> Clear a mark.
---------------------------------------------------
A mark is any upper-case or lower-case letter.
Certain marks are predefined:
^ means beginning of the file
$ means end of the file
---------------------------------------------------------------------------
CCHHAANNGGIINNGG FFIILLEESS
:e [_f_i_l_e] Examine a new file.
^X^V Same as :e.
:n * Examine the (_N-th) next file from the command line.
:p * Examine the (_N-th) previous file from the command line.
:x * Examine the first (or _N-th) file from the command line.
:d Delete the current file from the command line list.
= ^G :f Print current file name.
---------------------------------------------------------------------------
MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS
-_<_f_l_a_g_> Toggle a command line option [see OPTIONS below].
--_<_n_a_m_e_> Toggle a command line option, by name.
__<_f_l_a_g_> Display the setting of a command line option.
___<_n_a_m_e_> Display the setting of an option, by name.
+_c_m_d Execute the less cmd each time a new file is examined.
!_c_o_m_m_a_n_d Execute the shell command with $SHELL.
#_c_o_m_m_a_n_d Execute the shell command, expanded like a prompt.
|XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command.
s _f_i_l_e Save input to a file.
v Edit the current file with $VISUAL or $EDITOR.
V Print version number of "less".
---------------------------------------------------------------------------
OOPPTTIIOONNSS
Most options may be changed either on the command line,
or from within less by using the - or -- command.
Options may be given in one of two forms: either a single
character preceded by a -, or a name preceded by --.
-? ........ --help
Display help (from command line).
-a ........ --search-skip-screen
Search skips current screen.
-A ........ --SEARCH-SKIP-SCREEN
Search starts just after target line.
-b [_N] .... --buffers=[_N]
Number of buffers.
-B ........ --auto-buffers
Don't automatically allocate buffers for pipes.
-c ........ --clear-screen
Repaint by clearing rather than scrolling.
-d ........ --dumb
Dumb terminal.
-D xx_c_o_l_o_r . --color=xx_c_o_l_o_r
Set screen colors.
-e -E .... --quit-at-eof --QUIT-AT-EOF
Quit at end of file.
-f ........ --force
Force open non-regular files.
-F ........ --quit-if-one-screen
Quit if entire file fits on first screen.
-g ........ --hilite-search
Highlight only last match for searches.
-G ........ --HILITE-SEARCH
Don't highlight any matches for searches.
-h [_N] .... --max-back-scroll=[_N]
Backward scroll limit.
-i ........ --ignore-case
Ignore case in searches that do not contain uppercase.
-I ........ --IGNORE-CASE
Ignore case in all searches.
-j [_N] .... --jump-target=[_N]
Screen position of target lines.
-J ........ --status-column
Display a status column at left edge of screen.
-k [_f_i_l_e] . --lesskey-file=[_f_i_l_e]
Use a lesskey file.
-K ........ --quit-on-intr
Exit less in response to ctrl-C.
-L ........ --no-lessopen
Ignore the LESSOPEN environment variable.
-m -M .... --long-prompt --LONG-PROMPT
Set prompt style.
-n ......... --line-numbers
Suppress line numbers in prompts and messages.
-N ......... --LINE-NUMBERS
Display line number at start of each line.
-o [_f_i_l_e] . --log-file=[_f_i_l_e]
Copy to log file (standard input only).
-O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e]
Copy to log file (unconditionally overwrite).
-p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n]
Start at pattern (from command line).
-P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t]
Define new prompt.
-q -Q .... --quiet --QUIET --silent --SILENT
Quiet the terminal bell.
-r -R .... --raw-control-chars --RAW-CONTROL-CHARS
Output "raw" control characters.
-s ........ --squeeze-blank-lines
Squeeze multiple blank lines.
-S ........ --chop-long-lines
Chop (truncate) long lines rather than wrapping.
-t [_t_a_g] .. --tag=[_t_a_g]
Find a tag.
-T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e]
Use an alternate tags file.
-u -U .... --underline-special --UNDERLINE-SPECIAL
Change handling of backspaces, tabs and carriage returns.
-V ........ --version
Display the version number of "less".
-w ........ --hilite-unread
Highlight first new line after forward-screen.
-W ........ --HILITE-UNREAD
Highlight first new line after any forward movement.
-x [_N[,...]] --tabs=[_N[,...]]
Set tab stops.
-X ........ --no-init
Don't use termcap init/deinit strings.
-y [_N] .... --max-forw-scroll=[_N]
Forward scroll limit.
-z [_N] .... --window=[_N]
Set size of window.
-" [_c[_c]] . --quotes=[_c[_c]]
Set shell quote characters.
-~ ........ --tilde
Don't display tildes after end of file.
-# [_N] .... --shift=[_N]
Set horizontal scroll amount (0 = one half screen width).
--exit-follow-on-close
Exit F command on a pipe when writer closes pipe.
--file-size
Automatically determine the size of the input file.
--follow-name
The F command changes files if the input file is renamed.
--header=[_N[,_M]]
Use N lines and M columns to display file headers.
--incsearch
Search file as each pattern character is typed in.
--intr=_C
Use _C instead of ^X to interrupt a read.
--line-num-width=_N
Set the width of the -N line number field to _N characters.
--modelines=_N
Read _N lines from the input file and look for vim modelines.
--mouse
Enable mouse input.
--no-keypad
Don't send termcap keypad init/deinit strings.
--no-histdups
Remove duplicates from command history.
--no-number-headers
Don't give line numbers to header lines.
--no-search-headers
Don't search in header lines or columns.
--no-vbell
Disable the terminal's visual bell.
--redraw-on-quit
Redraw final screen when quitting.
--rscroll=_C
Set the character used to mark truncated lines.
--save-marks
Retain marks across invocations of less.
--search-options=[EFKNRW-]
Set default options for every search.
--show-preproc-errors
Display a message if preprocessor exits with an error status.
--proc-backspace
Process backspaces for bold/underline.
--SPECIAL-BACKSPACE
Treat backspaces as control characters.
--proc-return
Delete carriage returns before newline.
--SPECIAL-RETURN
Treat carriage returns as control characters.
--proc-tab
Expand tabs to spaces.
--SPECIAL-TAB
Treat tabs as control characters.
--status-col-width=_N
Set the width of the -J status column to _N characters.
--status-line
Highlight or color the entire line containing a mark.
--use-backslash
Subsequent options use backslash as escape char.
--use-color
Enables colored text.
--wheel-lines=_N
Each click of the mouse wheel moves _N lines.
--wordwrap
Wrap lines at spaces.
---------------------------------------------------------------------------
LLIINNEE EEDDIITTIINNGG
These keys can be used to edit text being entered
on the "command line" at the bottom of the screen.
RightArrow ..................... ESC-l ... Move cursor right one character.
LeftArrow ...................... ESC-h ... Move cursor left one character.
ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word.
ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word.
HOME ........................... ESC-0 ... Move cursor to start of line.
END ............................ ESC-$ ... Move cursor to end of line.
BACKSPACE ................................ Delete char to left of cursor.
DELETE ......................... ESC-x ... Delete char under cursor.
ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor.
ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor.
ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line.
UpArrow ........................ ESC-k ... Retrieve previous command line.
DownArrow ...................... ESC-j ... Retrieve next command line.
TAB ...................................... Complete filename & cycle.
SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle.
ctrl-L ................................... Complete filename, list all.

View File

@@ -32,6 +32,7 @@ import {
JobsModule,
EventBusModule,
NiucloudModule,
SysModule,
} from './common';
import {
TracingModule,
@@ -53,6 +54,16 @@ import { OutboxKafkaForwarderModule } from './core/event/outboxKafkaForwarder.mo
// 新增Site和Pay模块
import { SiteModule } from './common/site/site.module';
import { PayModule } from './common/pay/pay.module';
// 新增:其他业务模块
import { WechatModule } from './common/wechat/wechat.module';
import { WeappModule } from './common/weapp/weapp.module';
import { AddonModule } from './common/addon/addon.module';
import { DiyModule } from './common/diy/diy.module';
import { StatModule } from './common/stat/stat.module';
import { NoticeModule } from './common/notice/notice.module';
import { ChannelModule } from './common/channel/channel.module';
import { HomeModule } from './common/home/home.module';
import { LoginModule } from './common/login/login.module';
@Module({
imports: [
@@ -103,6 +114,28 @@ import { PayModule } from './common/pay/pay.module';
}),
// 认证模块
JwtGlobalModule,
AuthModule,
// 其他业务模块
SiteModule,
PayModule,
SysModule,
MemberModule,
AdminModule,
RbacModule,
UserModule,
JobsModule,
EventBusModule,
NiucloudModule,
// 新增业务模块
WechatModule,
WeappModule,
AddonModule,
DiyModule,
StatModule,
NoticeModule,
ChannelModule,
HomeModule,
LoginModule,
],
})
export class AppModule {}

View File

@@ -11,10 +11,12 @@ import {
} from '@nestjs/common';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { AddonDevelopService } from '../../services/admin/AddonDevelopService';
@Controller('adminapi/addon/develop')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AddonDevelopController {
constructor(private readonly addonDevelopService: AddonDevelopService) {}

View File

@@ -11,10 +11,12 @@ import {
} from '@nestjs/common';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { AddonAppService } from '../../services/admin/AddonAppService';
@Controller('adminapi/addon/app')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AppController {
constructor(private readonly addonAppService: AddonAppService) {}

View File

@@ -10,10 +10,12 @@ import {
} from '@nestjs/common';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { BackupService } from '../../services/admin/BackupService';
@Controller('adminapi/addon/backup')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class BackupController {
constructor(private readonly backupService: BackupService) {}

View File

@@ -18,7 +18,14 @@ import { AddonService } from '../../services/admin/AddonService';
export class UpgradeController {
constructor(private readonly addonService: AddonService) {}
@Post('upgrade/:addon?')
@Post('upgrade')
async upgradeNoAddon(
@Body() dto: { is_need_backup?: boolean; is_need_cloudbuild?: boolean },
) {
return this.addonService.upgrade('', dto);
}
@Post('upgrade/:addon')
async upgrade(
@Param('addon') addon: string,
@Body() dto: { is_need_backup?: boolean; is_need_cloudbuild?: boolean },
@@ -31,7 +38,12 @@ export class UpgradeController {
return this.addonService.executeUpgrade();
}
@Get('upgrade-content/:addon?')
@Get('upgrade-content')
async getUpgradeContentNoAddon() {
return this.addonService.getUpgradeContent('');
}
@Get('upgrade-content/:addon')
async getUpgradeContent(@Param('addon') addon: string) {
return this.addonService.getUpgradeContent(addon);
}
@@ -41,7 +53,12 @@ export class UpgradeController {
return this.addonService.getUpgradeTask();
}
@Get('upgrade-pre-check/:addon?')
@Get('upgrade-pre-check')
async upgradePreCheckNoAddon() {
return this.addonService.upgradePreCheck('');
}
@Get('upgrade-pre-check/:addon')
async upgradePreCheck(@Param('addon') addon: string) {
return this.addonService.upgradePreCheck(addon);
}

View File

@@ -10,6 +10,7 @@ import {
UseGuards,
UsePipes,
ValidationPipe,
UnauthorizedException,
} from '@nestjs/common';
import {
ApiTags,
@@ -52,8 +53,11 @@ export class AdminController {
@ApiResponse({ status: 200, description: '获取管理员列表成功' })
async getAdminList(
@Query() query: QueryAdminDto,
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
return await this.adminService.getAdminList(query, site_id);
}
@@ -63,8 +67,11 @@ export class AdminController {
@ApiResponse({ status: 200, description: '获取管理员详情成功' })
async getAdminDetail(
@Param('id') id: number,
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
return await this.adminService.getAdminDetail(id, site_id);
}
@@ -75,8 +82,11 @@ export class AdminController {
async updateAdmin(
@Param('id') id: number,
@Body() updateAdminDto: UpdateAdminDto,
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
return await this.adminService.updateAdmin(id, updateAdminDto, site_id);
}
@@ -86,8 +96,11 @@ export class AdminController {
@ApiResponse({ status: 200, description: '管理员删除成功' })
async deleteAdmin(
@Param('id') id: number,
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
await this.adminService.deleteAdmin(id, site_id);
return { message: '删除成功' };
}
@@ -98,8 +111,11 @@ export class AdminController {
@ApiResponse({ status: 200, description: '批量删除成功' })
async batchDeleteAdmins(
@Body() data: { uids: number[] },
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
await this.adminService.batchDeleteAdmins(data.uids, site_id);
return { message: '批量删除成功' };
}
@@ -110,8 +126,11 @@ export class AdminController {
@ApiResponse({ status: 200, description: '批量更新状态成功' })
async batchUpdateAdminStatus(
@Body() data: BatchUpdateAdminStatusDto,
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
await this.adminService.batchUpdateAdminStatus(
data.uids,
data.status,
@@ -126,8 +145,11 @@ export class AdminController {
@ApiResponse({ status: 200, description: '批量分配角色成功' })
async batchAssignAdminRoles(
@Body() data: BatchAssignRoleDto,
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
await this.adminService.batchAssignAdminRoles(
data.uids,
data.role_ids,
@@ -143,8 +165,11 @@ export class AdminController {
async resetAdminPassword(
@Param('id') id: number,
@Body() resetPasswordDto: ResetAdminPasswordDto,
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
await this.adminService.resetAdminPassword(id, resetPasswordDto, site_id);
return { message: '密码重置成功' };
}
@@ -156,8 +181,11 @@ export class AdminController {
async updateAdminStatus(
@Param('id') id: number,
@Body() data: { status: number },
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
await this.adminService.updateAdminStatus(id, data.status, site_id);
return { message: '状态更新成功' };
}
@@ -169,8 +197,11 @@ export class AdminController {
async assignAdminRoles(
@Param('id') id: number,
@Body() data: { role_ids: string },
@Query('site_id') site_id: number = 0,
@Query('site_id') site_id: number,
) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
await this.adminService.assignAdminRoles(id, data.role_ids, site_id);
return { message: '角色分配成功' };
}
@@ -179,7 +210,10 @@ export class AdminController {
@Roles('admin')
@ApiOperation({ summary: '导出管理员列表' })
@ApiResponse({ status: 200, description: '导出成功' })
async exportAdmins(@Query('site_id') site_id: number = 0) {
async exportAdmins(@Query('site_id') site_id: number) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
return await this.adminService.exportAdmins(site_id);
}
@@ -187,7 +221,10 @@ export class AdminController {
@Roles('admin')
@ApiOperation({ summary: '获取管理员统计信息' })
@ApiResponse({ status: 200, description: '获取统计信息成功' })
async getAdminStats(@Query('site_id') site_id: number = 0) {
async getAdminStats(@Query('site_id') site_id: number) {
if (!site_id) {
throw new UnauthorizedException('site_id is required');
}
return await this.adminService.getAdminStats(site_id);
}
}

View File

@@ -1,10 +1,9 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { SysUserRole } from './SysUserRole';
import { SysUserLog } from './SysUserLog';
@Entity('sys_user')
export class SysUser extends BaseEntity {
export class SysUser {
@PrimaryGeneratedColumn({ name: 'uid' })
uid: number;
@@ -32,6 +31,18 @@ export class SysUser extends BaseEntity {
@Column({ name: 'status', type: 'tinyint', default: 1 })
status: number;
@Column({ name: 'is_del', type: 'tinyint', default: 0 })
is_del: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0 })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0 })
update_time: number;
@DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0 })
delete_time: number;
// 关联关系
@OneToMany(() => SysUserRole, (userRole) => userRole.user)
user_role: SysUserRole[];

View File

@@ -23,19 +23,16 @@ export class SysUserRole {
@Column({ name: 'role_ids', type: 'varchar', length: 255, default: '' })
role_ids: string;
@CreateDateColumn({ name: 'create_time', type: 'int' })
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '添加时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int' })
update_time: number;
@Column({ name: 'is_admin', type: 'int', default: 0 })
@Column({ name: 'is_admin', type: 'int', default: 0, comment: '是否是超级管理员' })
is_admin: number;
@Column({ name: 'status', type: 'int', default: 1 })
@Column({ name: 'status', type: 'int', default: 1, comment: '状态' })
status: number;
@Column({ name: 'delete_time', type: 'int', default: 0 })
@Column({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
delete_time: number;
// 关联关系

View File

@@ -1,8 +1,9 @@
import { Module, forwardRef, Global } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { AuthToken } from './entities/AuthToken';
import { SysConfig } from '../settings/entities/sys-config.entity';
import { SysUser } from '../admin/entities/SysUser';
import { AuthService } from './services/AuthService';
import { AuthController } from './controllers/AuthController';
import { LoginApiController } from './controllers/api/LoginApiController';
@@ -21,6 +22,7 @@ import { CoreLoginConfigService } from './services/core/CoreLoginConfigService';
import { JwtAuthGuard } from './guards/JwtAuthGuard';
import { RolesGuard } from './guards/RolesGuard';
import { JwtGlobalModule } from './jwt.module';
import { RedisProvider } from '../../vendor/redis/redis.provider';
// 导入Admin和Member模块
import { AdminModule } from '../admin/admin.module';
@@ -30,45 +32,46 @@ import { MemberModule } from '../member/member.module';
@Module({
imports: [
PassportModule,
TypeOrmModule.forFeature([AuthToken]),
TypeOrmModule.forFeature([AuthToken, SysConfig, SysUser]),
JwtGlobalModule,
// 导入Admin和Member模块以使用其服务
forwardRef(() => AdminModule),
forwardRef(() => MemberModule),
],
providers: [
AuthService,
LoginApiService,
AuthService,
LoginApiService,
LoginConfigApiService,
RegisterApiService,
CaptchaService,
CaptchaService,
LoginConfigService,
CoreAuthService,
CoreCaptchaService,
CoreAuthService,
CoreCaptchaService,
CoreLoginConfigService,
JwtAuthGuard,
RolesGuard
RedisProvider,
JwtAuthGuard,
RolesGuard,
],
controllers: [
AuthController,
LoginApiController,
AuthController,
LoginApiController,
LoginConfigApiController,
RegisterApiController,
CaptchaController,
LoginConfigController
CaptchaController,
LoginConfigController,
],
exports: [
AuthService,
LoginApiService,
AuthService,
LoginApiService,
LoginConfigApiService,
RegisterApiService,
CaptchaService,
CaptchaService,
LoginConfigService,
CoreAuthService,
CoreCaptchaService,
CoreAuthService,
CoreCaptchaService,
CoreLoginConfigService,
JwtAuthGuard,
RolesGuard
JwtAuthGuard,
RolesGuard,
],
})
export class AuthModule {}

View File

@@ -15,23 +15,20 @@ export class CaptchaController {
@ApiOperation({ summary: '创建验证码' })
@ApiResponse({ status: 200, description: '创建成功' })
async create(@Query() query: CaptchaCreateDto) {
const data = await this.captchaService.create(query);
return { code: 200, message: '创建成功', data };
return await this.captchaService.create(query);
}
@Post('check')
@ApiOperation({ summary: '一次校验验证码' })
@ApiResponse({ status: 200, description: '校验成功' })
async check(@Body() body: CaptchaCheckDto) {
const data = await this.captchaService.check(body);
return { code: 200, message: '校验成功', data };
return await this.captchaService.check(body);
}
@Post('verification')
@ApiOperation({ summary: '二次校验验证码' })
@ApiResponse({ status: 200, description: '校验成功' })
async verification(@Body() body: CaptchaVerificationDto) {
const data = await this.captchaService.verification(body);
return { code: 200, message: '校验成功', data };
return await this.captchaService.verification(body);
}
}

View File

@@ -1,10 +1,11 @@
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
import { Controller, Get, Post, Body, UseGuards, Request, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../guards/JwtAuthGuard';
import { RolesGuard } from '../../guards/RolesGuard';
import { Roles } from '../../decorators/RolesDecorator';
import { LoginConfigService } from '../../services/admin/LoginConfigService';
import { LoginConfigDto } from '../../dto/admin/LoginConfigDto';
import { LoginConfig } from '../../services/core/CoreLoginConfigService';
@ApiTags('登录配置管理')
@Controller('adminapi/auth/login-config')
@@ -16,16 +17,22 @@ export class LoginConfigController {
@Get('config')
@ApiOperation({ summary: '获取登录设置' })
@ApiResponse({ status: 200, description: '获取成功' })
async getConfig() {
const data = await this.loginConfigService.getConfig();
return { code: 200, message: '获取成功', data };
async getConfig(@Request() req: any): Promise<LoginConfig> {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.loginConfigService.getConfig(siteId);
}
@Post('config')
@ApiOperation({ summary: '设置登录配置' })
@ApiResponse({ status: 200, description: '设置成功' })
async setConfig(@Body() body: LoginConfigDto) {
const data = await this.loginConfigService.setConfig(body);
return { code: 200, message: '设置成功', data };
async setConfig(@Request() req: any, @Body() body: LoginConfigDto) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.loginConfigService.setConfig(body, siteId);
}
}

View File

@@ -8,6 +8,7 @@ import {
} from '@nestjs/common';
import { Public } from '../../../auth/decorators/public.decorator';
import { LoginConfigApiService } from '../../services/api/LoginConfigApiService';
import { LoginConfig } from '../../services/core/CoreLoginConfigService';
@Controller('api/login/config')
export class LoginConfigApiController {
@@ -18,7 +19,7 @@ export class LoginConfigApiController {
*/
@Get('info')
@Public()
async getInfo(@Query() query: any) {
async getInfo(@Query() query: any): Promise<LoginConfig> {
return this.loginConfigApiService.getInfo(query);
}

View File

@@ -48,4 +48,26 @@ export class LoginConfigDto {
lockoutDuration?: number;
lockoutType?: string;
};
// PHP 特有字段
@ApiProperty({ description: '是否启用授权注册', required: false })
@IsOptional()
isAuthRegister?: boolean;
@ApiProperty({ description: '是否强制获取用户信息', required: false })
@IsOptional()
isForceAccessUserInfo?: boolean;
@ApiProperty({ description: '是否绑定手机号', required: false })
@IsOptional()
isBindMobile?: boolean;
@ApiProperty({ description: '是否显示协议', required: false })
@IsOptional()
agreementShow?: boolean;
@ApiProperty({ description: '描述信息', required: false })
@IsOptional()
@IsString()
desc?: string;
}

View File

@@ -1,16 +1,16 @@
import { Injectable } from '@nestjs/common';
import { CoreLoginConfigService } from '../core/CoreLoginConfigService';
import { CoreLoginConfigService, LoginConfig } from '../core/CoreLoginConfigService';
import { LoginConfigDto } from '../../dto/admin/LoginConfigDto';
@Injectable()
export class LoginConfigService {
constructor(private readonly coreLoginConfig: CoreLoginConfigService) {}
async getConfig() {
return await this.coreLoginConfig.getConfig();
async getConfig(siteId: number): Promise<LoginConfig> {
return await this.coreLoginConfig.getConfig(siteId);
}
async setConfig(dto: LoginConfigDto) {
return await this.coreLoginConfig.setConfig(dto);
async setConfig(dto: LoginConfigDto, siteId: number) {
return await this.coreLoginConfig.setConfig(dto, siteId);
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { CoreLoginConfigService } from '../core/CoreLoginConfigService';
@Injectable()
@@ -9,35 +9,55 @@ export class LoginConfigApiService {
* 获取登录配置
*/
async getInfo(query: any) {
return this.coreLoginConfigService.getInfo(query);
const siteId = query.site_id;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return this.coreLoginConfigService.getInfo(siteId, query);
}
/**
* 获取登录方式
*/
async getMethods(query: any) {
return this.coreLoginConfigService.getMethods(query);
const siteId = query.site_id;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return this.coreLoginConfigService.getMethods(siteId, query);
}
/**
* 获取验证码配置
*/
async getCaptchaConfig(query: any) {
return this.coreLoginConfigService.getCaptchaConfig(query);
const siteId = query.site_id;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return this.coreLoginConfigService.getCaptchaConfig(siteId, query);
}
/**
* 获取第三方登录配置
*/
async getThirdPartyConfig(query: any) {
return this.coreLoginConfigService.getThirdPartyConfig(query);
const siteId = query.site_id;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return this.coreLoginConfigService.getThirdPartyConfig(siteId, query);
}
/**
* 获取注册配置
*/
async getRegisterConfig(query: any) {
return this.coreLoginConfigService.getRegisterConfig(query);
const siteId = query.site_id;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return this.coreLoginConfigService.getRegisterConfig(siteId, query);
}
/**

View File

@@ -1,26 +1,23 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { SysUser } from '../../../admin/entities/SysUser';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
@Injectable()
export class CoreAuthService extends BaseService<SysUser> {
export class CoreAuthService {
constructor(
@InjectRepository(SysUser)
private userRepository: Repository<SysUser>,
) {
super(userRepository);
}
private readonly userRepository: Repository<SysUser>,
) {}
/**
* 验证用户凭据
*/
async validateUser(username: string, password: string, site_id: number) {
const user = await this.userRepository.findOne({
where: { username, site_id, status: 1 },
where: { username, status: 1 },
});
if (!user) {
@@ -49,7 +46,7 @@ export class CoreAuthService extends BaseService<SysUser> {
*/
async checkUserExists(username: string, site_id: number) {
const user = await this.userRepository.findOne({
where: { username, site_id },
where: { username },
});
return !!user;
}
@@ -60,15 +57,20 @@ export class CoreAuthService extends BaseService<SysUser> {
async createUser(userData: any) {
const hashedPassword = await bcrypt.hash(userData.password, 10);
const user = this.userRepository.create({
const userDataWithHash = {
...userData,
password: hashedPassword,
status: 1,
create_time: Math.floor(Date.now() / 1000),
});
};
const saved = await this.userRepository.save(user);
return Array.isArray(saved) ? saved[0] : saved;
const user = this.userRepository.create({
...userDataWithHash,
create_time: Math.floor(Date.now() / 1000),
update_time: Math.floor(Date.now() / 1000),
is_del: 0,
delete_time: 0,
} as any);
return await this.userRepository.save(user as any);
}
/**

View File

@@ -1,17 +1,26 @@
import { Injectable } from '@nestjs/common';
import { RedisProvider } from '../../../../vendor/redis/redis.provider';
import { CaptchaCreateDto, CaptchaCheckDto, CaptchaVerificationDto } from '../../dto/admin/CaptchaDto';
@Injectable()
export class CoreCaptchaService {
constructor() {}
private readonly CAPTCHA_PREFIX = 'captcha:';
private readonly CAPTCHA_TTL_SECONDS = 300; // 5 min
constructor(private readonly redisProvider: RedisProvider) {}
async create(dto: CaptchaCreateDto) {
// 对齐 PHP: CaptchaService->create()
const captchaId = this.generateCaptchaId();
const captchaValue = this.generateCaptchaValue(dto.length || 4);
// TODO: 生成验证码图片并存储
// const captchaImage = await this.generateCaptchaImage(captchaValue, dto);
// 持久化到 Redis
const client = this.redisProvider.getClient();
await client.setex(
`${this.CAPTCHA_PREFIX}${captchaId}`,
this.CAPTCHA_TTL_SECONDS,
captchaValue,
);
return {
captchaId,
@@ -23,7 +32,6 @@ export class CoreCaptchaService {
async check(dto: CaptchaCheckDto) {
// 对齐 PHP: CaptchaService->check()
// TODO: 从缓存或数据库验证验证码
const isValid = await this.validateCaptcha(dto.captchaId, dto.captchaValue);
if (!isValid) {
@@ -35,7 +43,6 @@ export class CoreCaptchaService {
async verification(dto: CaptchaVerificationDto) {
// 对齐 PHP: CaptchaService->verification()
// TODO: 二次验证逻辑,可能包括短信验证、邮箱验证等
const isValid = await this.validateCaptcha(dto.captchaId, dto.captchaValue);
if (!isValid) {
@@ -71,14 +78,20 @@ export class CoreCaptchaService {
}
private async validateCaptcha(captchaId: string, captchaValue: string): Promise<boolean> {
// TODO: 从Redis或数据库验证验证码
// 临时实现
return captchaValue.length >= 4;
const client = this.redisProvider.getClient();
const key = `${this.CAPTCHA_PREFIX}${captchaId}`;
const stored = await client.get(key);
if (!stored) return false;
const ok = stored.toLowerCase() === (captchaValue || '').toLowerCase();
if (ok) {
// 一次性验证码:校验成功后删除
await client.del(key);
}
return ok;
}
private async performSecondVerification(params?: Record<string, any>): Promise<boolean> {
// TODO: 实现二次验证逻辑
// 可能包括短信验证、邮箱验证、人脸识别等
// 可以在此扩展短信/邮箱等二次校验
return true;
}
}

View File

@@ -1,12 +1,99 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SysConfig } from '../../../settings/entities/sys-config.entity';
import { BaseService } from '../../../../core/base/BaseService';
import { LoginConfigDto } from '../../dto/admin/LoginConfigDto';
@Injectable()
export class CoreLoginConfigService {
constructor() {}
export interface LoginConfig {
isCaptcha: number;
isSiteCaptcha: number;
bg: string;
siteBg: string;
loginMethods: {
username: boolean;
email: boolean;
mobile: boolean;
wechat: boolean;
qq: boolean;
};
passwordPolicy: {
minLength: number;
requireSpecialChar: boolean;
requireNumber: boolean;
requireUppercase: boolean;
};
loginLimit: {
maxAttempts: number;
lockoutDuration: number;
lockoutType: string;
};
// PHP 特有字段
isAuthRegister: boolean;
isForceAccessUserInfo: boolean;
isBindMobile: boolean;
agreementShow: boolean;
desc: string;
}
@Injectable()
export class CoreLoginConfigService extends BaseService<SysConfig> {
constructor(
@InjectRepository(SysConfig)
configRepository: Repository<SysConfig>,
) {
super(configRepository);
}
async getConfig(siteId: number): Promise<LoginConfig> {
// 对齐 PHP: CoreMemberConfigService->getLoginConfig()
const config = await this.repository.findOne({
where: {
config_key: 'LOGIN',
site_id: siteId
},
});
if (config?.value) {
let configData: any;
try {
configData = JSON.parse(config.value);
} catch {
configData = {};
}
return {
isCaptcha: 1, // 默认启用验证码
isSiteCaptcha: 1, // 默认启用站点验证码
bg: configData.bg_url || '', // 登录背景图
siteBg: configData.bg_url || '', // 站点登录背景图
loginMethods: {
username: configData.is_username === 1,
email: false, // PHP 中没有邮箱登录
mobile: configData.is_mobile === 1,
wechat: false, // 微信登录通过其他方式处理
qq: false,
},
passwordPolicy: {
minLength: 6,
requireSpecialChar: false,
requireNumber: false,
requireUppercase: false,
},
loginLimit: {
maxAttempts: 5,
lockoutDuration: 30, // 分钟
lockoutType: 'ip', // ip 或 username
},
// PHP 特有字段
isAuthRegister: configData.is_auth_register === 1,
isForceAccessUserInfo: configData.is_force_access_user_info === 1,
isBindMobile: configData.is_bind_mobile === 1,
agreementShow: configData.agreement_show === 1,
desc: configData.desc || '精选好物,购物优惠的省钱平台',
};
}
async getConfig() {
// 对齐 PHP: ConfigService->getConfig()
return {
isCaptcha: 1, // 默认启用验证码
isSiteCaptcha: 1, // 默认启用站点验证码
@@ -14,8 +101,8 @@ export class CoreLoginConfigService {
siteBg: '', // 站点登录背景图
loginMethods: {
username: true,
email: true,
mobile: true,
email: false,
mobile: false,
wechat: false,
qq: false,
},
@@ -30,68 +117,81 @@ export class CoreLoginConfigService {
lockoutDuration: 30, // 分钟
lockoutType: 'ip', // ip 或 username
},
// PHP 特有字段
isAuthRegister: true,
isForceAccessUserInfo: false,
isBindMobile: false,
agreementShow: false,
desc: '精选好物,购物优惠的省钱平台',
};
}
async setConfig(dto: LoginConfigDto) {
// 对齐 PHP: ConfigService->setConfig()
async setConfig(dto: LoginConfigDto, siteId: number) {
// 对齐 PHP: CoreMemberConfigService->setLoginConfig()
const config = {
isCaptcha: dto.isCaptcha ?? 1,
isSiteCaptcha: dto.isSiteCaptcha ?? 1,
bg: dto.bg ?? '',
siteBg: dto.siteBg ?? '',
loginMethods: dto.loginMethods ?? {
username: true,
email: true,
mobile: true,
wechat: false,
qq: false,
},
passwordPolicy: dto.passwordPolicy ?? {
minLength: 6,
requireSpecialChar: false,
requireNumber: false,
requireUppercase: false,
},
loginLimit: dto.loginLimit ?? {
maxAttempts: 5,
lockoutDuration: 30,
lockoutType: 'ip',
},
is_username: dto.loginMethods?.username ? 1 : 0,
is_mobile: dto.loginMethods?.mobile ? 1 : 0,
is_auth_register: dto.isAuthRegister ? 1 : 0,
is_force_access_user_info: dto.isForceAccessUserInfo ? 1 : 0,
is_bind_mobile: dto.isBindMobile ? 1 : 0,
agreement_show: dto.agreementShow ? 1 : 0,
bg_url: dto.bg || '',
desc: dto.desc || '精选好物,购物优惠的省钱平台',
};
// TODO: 保存配置到数据库或配置文件
// await this.saveConfig(config);
const existed = await this.repository.findOne({
where: {
config_key: 'LOGIN',
site_id: siteId
},
});
if (existed) {
await this.update(existed.id, {
value: JSON.stringify(config),
});
} else {
await this.create({
site_id: siteId,
config_key: 'LOGIN',
value: JSON.stringify(config),
});
}
return { success: true, message: '配置保存成功' };
}
// 兼容 API 层调用的方法(按 PHP 语义拆分)
async getInfo(query?: any) {
return this.getConfig();
async getInfo(siteId: number, _query?: any): Promise<LoginConfig> {
return this.getConfig(siteId);
}
async getMethods(query?: any) {
const config = await this.getConfig();
async getMethods(siteId: number, _query?: any) {
const config = await this.getConfig(siteId);
return config.loginMethods;
}
async getCaptchaConfig(query?: any) {
const config = await this.getConfig();
async getCaptchaConfig(siteId: number, _query?: any) {
const config = await this.getConfig(siteId);
return { isCaptcha: config.isCaptcha, isSiteCaptcha: config.isSiteCaptcha };
}
async getThirdPartyConfig(query?: any) {
const config = await this.getConfig();
async getThirdPartyConfig(siteId: number, _query?: any) {
const config = await this.getConfig(siteId);
return { wechat: config.loginMethods.wechat, qq: config.loginMethods.qq };
}
async getRegisterConfig(query?: any) {
const config = await this.getConfig();
async getRegisterConfig(siteId: number, _query?: any) {
const config = await this.getConfig(siteId);
return { passwordPolicy: config.passwordPolicy };
}
async getForgotPasswordConfig(query?: any) {
getForgotPasswordConfig(_query?: any) {
return { ways: ['email', 'mobile'] };
}
private buildConfigKey(siteId: number): string {
// 兼容无 site_id 字段的实体定义,使用 key 后缀区分站点
return `login_config:site:${siteId || 0}`;
}
}

View File

@@ -1,36 +1,50 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { DiyFormFields } from './DiyFormFields';
import { DiyFormRecords } from './DiyFormRecords';
@Entity('diy_form')
export class DiyForm extends BaseEntity {
export class DiyForm {
@PrimaryGeneratedColumn({ name: 'form_id' })
form_id: number;
@Column({ name: 'title', type: 'varchar', length: 255, comment: '表单标题' })
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ name: 'page_title', type: 'varchar', length: 255, default: '', comment: '表单名称(用于后台展示)' })
page_title: string;
@Column({ name: 'title', type: 'varchar', length: 255, default: '', comment: '表单名称(用于前台展示)' })
title: string;
@Column({ name: 'type', type: 'varchar', length: 50, comment: '表单类型' })
@Column({ name: 'type', type: 'varchar', length: 255, default: '', comment: '表单类型' })
type: string;
@Column({ name: 'value', type: 'text', nullable: true, comment: '表单配置JSON' })
value: string;
@Column({ name: 'share', type: 'text', nullable: true, comment: '分享配置JSON' })
share: string;
@Column({ name: 'status', type: 'tinyint', default: 1, comment: '状态0=禁用,1=启用' })
@Column({ name: 'status', type: 'tinyint', default: 0, comment: '状态0关闭1开启' })
status: number;
@Column({ name: 'is_default', type: 'tinyint', default: 0, comment: '是否默认0=否,1=是' })
is_default: number;
@Column({ name: 'template', type: 'varchar', length: 255, default: '', comment: '模板名称' })
template: string;
@Column({ name: 'addon', type: 'varchar', length: 100, default: '', comment: '插件标识' })
@Column({ name: 'value', type: 'longtext', nullable: true, comment: '表单数据json格式包含展示组件' })
value: string;
@Column({ name: 'addon', type: 'varchar', length: 255, default: '', comment: '所属插件标识' })
addon: string;
@Column({ name: 'sort', type: 'int', default: 0, comment: '排序' })
sort: number;
@Column({ name: 'share', type: 'varchar', length: 1000, default: '', comment: '分享内容' })
share: string;
@Column({ name: 'write_num', type: 'int', default: 0, comment: '表单填写总数量' })
write_num: number;
@Column({ name: 'remark', type: 'varchar', length: 255, default: '', comment: '备注说明' })
remark: string;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '创建时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
update_time: number;
// 关联字段
@OneToMany(() => DiyFormFields, fields => fields.form)

View File

@@ -1,12 +1,11 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
/**
* 自定义页面实体
* 对应数据库表: diy_page
*/
@Entity('diy_page')
export class DiyPage extends BaseEntity {
export class DiyPage {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@@ -43,5 +42,12 @@ export class DiyPage extends BaseEntity {
@Column({ name: 'visit_count', type: 'int', default: 0, comment: '访问量' })
visit_count: number;
// create_time 和 update_time 由 BaseEntity 提供
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '创建时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
update_time: number;
}

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { CoreDiyService } from '../core/CoreDiyService';
import { DiyPage } from '../../entities/DiyPage';
@@ -74,7 +74,10 @@ export class DiyService {
* 控制器所需:分页
*/
async getPage(query: any) {
const { site_id = 0, key } = query || {};
const { site_id, key } = query || {};
if (!site_id) {
throw new Error('缺少 site_id 参数');
}
const list = await this.coreDiyService.getPageListByType(Number(site_id), key);
const page = Number(query?.page || 1);
const limit = Number(query?.limit || list.length || 20);
@@ -88,9 +91,12 @@ export class DiyService {
}
async add(data: any) {
if (!data.site_id) {
throw new UnauthorizedException('Missing site_id');
}
// 对齐 PHP 字段映射
const payload = {
site_id: data.site_id || 0,
site_id: data.site_id,
name: data.page_name,
type: data.page_type || data.key || 'custom',
value: JSON.stringify(data.page_data || {}),
@@ -118,7 +124,7 @@ export class DiyService {
const info = await this.coreDiyService.getPageInfo(pageId);
if (!info) return null;
return this.coreDiyService.addPage({
site_id: (info as any).site_id || 0,
site_id: (info as any).site_id,
name: newName,
type: (info as any).type,
value: (info as any).value,
@@ -136,7 +142,7 @@ export class DiyService {
const info = await this.coreDiyService.getPageInfo(pageId);
if (!info) return false;
// 对齐 PHP设置为默认
await this.coreDiyService.setDefaultPage((info as any).site_id || 0, (info as any).name, (info as any).id);
await this.coreDiyService.setDefaultPage((info as any).site_id, (info as any).name, (info as any).id);
return true;
}

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import { BaseService } from '../../../../core/base/BaseService';
import { DiyForm } from '../../entities/DiyForm';
import { DiyFormFields } from '../../entities/DiyFormFields';
import { DiyFormRecords } from '../../entities/DiyFormRecords';
@@ -14,7 +13,7 @@ import { DiyFormWriteConfig } from '../../entities/DiyFormWriteConfig';
* 对应PHP: CoreDiyFormService
*/
@Injectable()
export class CoreDiyFormService extends BaseService<DiyForm> {
export class CoreDiyFormService {
constructor(
@InjectRepository(DiyForm)
private readonly diyFormRepository: Repository<DiyForm>,
@@ -28,9 +27,7 @@ export class CoreDiyFormService extends BaseService<DiyForm> {
private readonly diyFormSubmitConfigRepository: Repository<DiyFormSubmitConfig>,
@InjectRepository(DiyFormWriteConfig)
private readonly diyFormWriteConfigRepository: Repository<DiyFormWriteConfig>,
) {
super(diyFormRepository);
}
) {}
/**
* 分页查询表单列表
@@ -160,9 +157,7 @@ export class CoreDiyFormService extends BaseService<DiyForm> {
'value',
'share',
'status',
'is_default',
'addon',
'sort',
'create_time',
'update_time',
],
@@ -297,13 +292,13 @@ export class CoreDiyFormService extends BaseService<DiyForm> {
// 先取消所有默认
await this.diyFormRepository.update(
{ site_id: siteId },
{ is_default: 0, update_time: Math.floor(Date.now() / 1000) },
{ status: 0, update_time: Math.floor(Date.now() / 1000) },
);
// 设置当前为默认
const result = await this.diyFormRepository.update(
{ form_id: formId, site_id: siteId },
{ is_default: 1, update_time: Math.floor(Date.now() / 1000) },
{ status: 1, update_time: Math.floor(Date.now() / 1000) },
);
return (result.affected || 0) > 0;

View File

@@ -1,13 +1,12 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { DiyPage } from '../../entities/DiyPage';
import { DiyRoute } from '../../entities/DiyRoute';
import { DiyTheme } from '../../entities/DiyTheme';
@Injectable()
export class CoreDiyService extends BaseService<DiyPage> {
export class CoreDiyService {
constructor(
@InjectRepository(DiyPage)
private diyPageRepository: Repository<DiyPage>,
@@ -15,9 +14,7 @@ export class CoreDiyService extends BaseService<DiyPage> {
private diyRouteRepository: Repository<DiyRoute>,
@InjectRepository(DiyTheme)
private diyThemeRepository: Repository<DiyTheme>,
) {
super(diyPageRepository);
}
) {}
/**
* 获取DIY页面列表
@@ -140,6 +137,33 @@ export class CoreDiyService extends BaseService<DiyPage> {
/**
* 设置默认页面
*/
/**
* 删除页面
*/
async delete(pageId: number) {
const result = await this.diyPageRepository.delete({ id: pageId });
return (result.affected || 0) > 0;
}
/**
* 创建页面
*/
async create(data: any) {
const page = this.diyPageRepository.create(data);
return await this.diyPageRepository.save(page);
}
/**
* 更新页面
*/
async update(pageId: number, data: any) {
const result = await this.diyPageRepository.update(
{ id: pageId },
{ ...data, update_time: Math.floor(Date.now() / 1000) }
);
return (result.affected || 0) > 0;
}
async setDefaultPage(site_id: number, name: string, page_id: number) {
// 先取消其他页面的默认状态
await this.diyPageRepository.update(

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { HomeSiteService } from './services/admin/HomeSiteService';
import { SiteController } from './controllers/adminapi/SiteController';
@Module({
controllers: [SiteController],
providers: [HomeSiteService],
exports: [HomeSiteService],
})
export class HomeModule {}

View File

@@ -1,5 +1,10 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { VendorModule } from '../../vendor';
import { PayModule } from '../pay/pay.module';
import { SiteModule } from '../site/site.module';
import { SysModule } from '../sys/sys.module';
import { ScheduleModule } from '../schedule/schedule.module';
import { MemberModule } from '../member/member.module';
import { JobsService } from './jobs.service';
import { OnModuleInit } from '@nestjs/common';
import { PaymentProcessors } from './processors/payment';
@@ -12,7 +17,7 @@ import { UpgradeProcessors } from './processors/upgrade';
import { WxoplatformProcessors } from './processors/wxoplatform';
@Module({
imports: [VendorModule],
imports: [VendorModule, forwardRef(() => PayModule), SiteModule, SysModule, ScheduleModule, MemberModule],
providers: [
JobsService,
PaymentProcessors,

View File

@@ -20,17 +20,17 @@ export class PaymentProcessors {
? await this.corePay.findByOutTradeNo(data.siteId, data.outTradeNo)
: await this.corePay.findByOutTradeNoUnsafe(data.outTradeNo);
if (!pay) return { skipped: true };
const resolved = await this.registry.resolve(pay.siteId, pay.type, pay.channel);
const resolved = await this.registry.resolve(pay.site_id, pay.type, pay.channel);
const adapter: PaymentAdapter = resolved.adapter as PaymentAdapter;
const config: unknown = resolved.config as unknown;
const q: { status: string; tradeNo?: string } = await adapter.query(
config as any,
pay.outTradeNo,
pay.out_trade_no,
);
if (q.status === 'SUCCESS') {
await this.corePay.updateStatus(pay.siteId, pay.outTradeNo, 1, q.tradeNo);
await this.corePay.updateStatus(pay.site_id, pay.out_trade_no, 1, q.tradeNo);
} else if (q.status === 'CLOSED') {
await this.corePay.updateStatus(pay.siteId, pay.outTradeNo, 2);
await this.corePay.updateStatus(pay.site_id, pay.out_trade_no, 2);
}
return { ok: true };
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { LoginService } from './services/admin/LoginService';
import { LoginController } from './controllers/adminapi/LoginController';
@Module({
controllers: [LoginController],
providers: [LoginService],
exports: [LoginService],
})
export class LoginModule {}

View File

@@ -5,8 +5,9 @@ import {
OneToMany,
ManyToOne,
JoinColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { MemberAccount } from './MemberAccount';
import { MemberCashOut } from './MemberCashOut';
import { MemberLabel } from './MemberLabel';
@@ -16,7 +17,7 @@ import { MemberAddress } from './MemberAddress';
import { MemberAccountLog } from './MemberAccountLog';
@Entity('member')
export class Member extends BaseEntity {
export class Member {
@PrimaryGeneratedColumn({ name: 'member_id' })
member_id: number;
@@ -26,6 +27,9 @@ export class Member extends BaseEntity {
@Column({ name: 'pid', type: 'int', default: 0 })
pid: number;
@Column({ name: 'site_id', type: 'int', default: 0 })
site_id: number;
@Column({ name: 'username', type: 'varchar', length: 255, default: '' })
username: string;
@@ -214,6 +218,15 @@ export class Member extends BaseEntity {
@Column({ name: 'remark', type: 'varchar', length: 300, default: '' })
remark: string;
@Column({ name: 'is_del', type: 'tinyint', default: 0 })
is_del: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0 })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0 })
update_time: number;
// 关联关系
@OneToMany(() => MemberAccount, (account) => account.member)
accounts: MemberAccount[];

View File

@@ -5,17 +5,19 @@ import {
ManyToOne,
JoinColumn,
} from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Member } from './Member';
@Entity('member_address')
export class MemberAddress extends BaseEntity {
export class MemberAddress {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ name: 'member_id', type: 'int', default: 0 })
@Column({ name: 'member_id', type: 'int', default: 0, comment: '会员id' })
member_id: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ name: 'name', type: 'varchar', length: 255, default: '' })
name: string;

View File

@@ -1,50 +1,37 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { BaseEntity } from '@wwjCore/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { Member } from './Member';
@Entity('member_level')
export class MemberLevel extends BaseEntity {
export class MemberLevel {
@PrimaryGeneratedColumn()
level_id: number;
@Column({ type: 'varchar', length: 50, comment: '等级名称' })
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ name: 'level_name', type: 'varchar', length: 50, default: '', comment: '等级名称' })
level_name: string;
@Column({ type: 'varchar', length: 255, comment: '等级图标' })
level_icon: string;
@Column({ name: 'growth', type: 'int', default: 0, comment: '所需成长值' })
growth: number;
@Column({ type: 'int', default: 0, comment: '升级所需积分' })
upgrade_point: number;
@Column({ name: 'remark', type: 'varchar', length: 255, default: '', comment: '备注' })
remark: string;
@Column({
type: 'decimal',
precision: 5,
scale: 2,
default: 1.0,
comment: '积分倍率',
})
point_rate: number;
@Column({
type: 'decimal',
precision: 5,
scale: 2,
default: 1.0,
comment: '折扣率',
})
discount_rate: number;
@Column({ type: 'int', default: 0, comment: '排序' })
sort: number;
@Column({ type: 'tinyint', default: 1, comment: '状态 1:启用 0:禁用' })
@Column({ name: 'status', type: 'int', default: 1, comment: '状态 0已禁用1已启用' })
status: number;
@Column({ type: 'varchar', length: 255, comment: '等级描述' })
description: string;
@Column({ name: 'level_benefits', type: 'text', nullable: true, comment: '等级权益' })
level_benefits: string;
@Column({ type: 'varchar', length: 255, comment: '等级权益' })
benefits: string;
@Column({ name: 'level_gifts', type: 'text', nullable: true, comment: '等级礼包' })
level_gifts: string;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '添加时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
update_time: number;
// 关联关系
@OneToMany(() => Member, (member) => member.level)

View File

@@ -12,6 +12,7 @@ import { MemberAccountLog } from './entities/MemberAccountLog';
import { MemberPoints } from './entities/MemberPoints';
import { MemberBalance } from './entities/MemberBalance';
import { MemberConfig } from './entities/MemberConfig';
import { SysConfig } from '../settings/entities/sys-config.entity';
import { CoreMemberService } from './services/core/CoreMemberService';
import { MemberService as MemberApiService } from './services/api/MemberService';
import { MemberAccountService } from './services/api/MemberAccountService';
@@ -65,6 +66,7 @@ import { MemberSignController } from './controllers/adminapi/MemberSignControlle
MemberPoints,
MemberBalance,
MemberConfig,
SysConfig,
]),
],
providers: [

View File

@@ -1,17 +1,14 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { MemberAddress } from '../../entities/MemberAddress';
@Injectable()
export class CoreMemberAddressService extends BaseService<MemberAddress> {
export class CoreMemberAddressService {
constructor(
@InjectRepository(MemberAddress)
private memberAddressRepository: Repository<MemberAddress>,
) {
super(memberAddressRepository);
}
) {}
/**
* 获取会员地址列表
@@ -26,7 +23,7 @@ export class CoreMemberAddressService extends BaseService<MemberAddress> {
where,
skip: (page - 1) * limit,
take: limit,
order: { is_default: 'DESC', create_time: 'DESC' },
order: { is_default: 'DESC' },
});
}
@@ -39,6 +36,33 @@ export class CoreMemberAddressService extends BaseService<MemberAddress> {
});
}
/**
* 创建地址
*/
async create(dto: any) {
const address = this.memberAddressRepository.create(dto);
return await this.memberAddressRepository.save(address);
}
/**
* 更新地址
*/
async update(address_id: number, dto: any) {
const result = await this.memberAddressRepository.update(
{ id: address_id },
dto
);
return (result.affected || 0) > 0;
}
/**
* 删除地址
*/
async delete(address_id: number) {
const result = await this.memberAddressRepository.delete({ id: address_id });
return (result.affected || 0) > 0;
}
/**
* 设置默认地址
*/

View File

@@ -1,17 +1,14 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { MemberLevel } from '../../entities/MemberLevel';
@Injectable()
export class CoreMemberLevelService extends BaseService<MemberLevel> {
export class CoreMemberLevelService {
constructor(
@InjectRepository(MemberLevel)
private memberLevelRepository: Repository<MemberLevel>,
) {
super(memberLevelRepository);
}
) {}
/**
* 获取会员等级列表
@@ -24,7 +21,7 @@ export class CoreMemberLevelService extends BaseService<MemberLevel> {
where,
skip: (page - 1) * limit,
take: limit,
order: { upgrade_point: 'ASC' },
order: { growth: 'ASC' },
});
}
@@ -37,19 +34,46 @@ export class CoreMemberLevelService extends BaseService<MemberLevel> {
});
}
/**
* 创建等级
*/
async create(dto: any) {
const level = this.memberLevelRepository.create(dto);
return await this.memberLevelRepository.save(level);
}
/**
* 更新等级
*/
async update(level_id: number, dto: any) {
const result = await this.memberLevelRepository.update(
{ level_id },
dto
);
return (result.affected || 0) > 0;
}
/**
* 删除等级
*/
async delete(level_id: number) {
const result = await this.memberLevelRepository.delete({ level_id });
return (result.affected || 0) > 0;
}
/**
* 设置默认等级
*/
async setDefault(level_id: number) {
// 先取消其他等级的默认状态
await this.memberLevelRepository.update(
{ sort: 0 },
{ sort: 1 }
{ level_id: level_id },
{ status: 1 }
);
// 设置当前等级为默认
const result = await this.memberLevelRepository.update(level_id, {
sort: 0,
const result = await this.memberLevelRepository.update({ level_id }, {
status: 1,
});
return (result.affected || 0) > 0;

View File

@@ -1,21 +1,18 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not, Between } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { Member } from '../../entities/Member';
import { MemberLevel } from '../../entities/MemberLevel';
import * as bcrypt from 'bcrypt';
@Injectable()
export class CoreMemberService extends BaseService<Member> {
export class CoreMemberService {
constructor(
@InjectRepository(Member)
private memberRepository: Repository<Member>,
@InjectRepository(MemberLevel)
private memberLevelRepository: Repository<MemberLevel>,
) {
super(memberRepository);
}
) {}
/**
* 创建会员
@@ -52,21 +49,28 @@ export class CoreMemberService extends BaseService<Member> {
* 根据ID获取会员
*/
async getMemberById(memberId: number): Promise<Member | null> {
return await this.findOne(memberId);
return await this.memberRepository.findOne({
where: { member_id: memberId, is_del: 0 },
});
}
/**
* 删除会员
*/
async deleteMember(memberId: number): Promise<void> {
await this.delete(memberId);
await this.memberRepository.update(
{ member_id: memberId },
{ is_del: 1, update_time: Math.floor(Date.now() / 1000) },
);
}
/**
* 根据ID查找会员
*/
async findById(memberId: number): Promise<Member> {
const member = await this.findOne(memberId);
const member = await this.memberRepository.findOne({
where: { member_id: memberId, is_del: 0 },
});
if (!member) {
throw new NotFoundException('会员不存在');
@@ -79,14 +83,18 @@ export class CoreMemberService extends BaseService<Member> {
* 根据用户名查找会员
*/
async findByUsername(username: string): Promise<Member | null> {
return await this.findOneBy({ username });
return await this.memberRepository.findOne({
where: { username, is_del: 0 },
});
}
/**
* 根据手机号查找会员
*/
async findByMobile(mobile: string): Promise<Member | null> {
return await this.findOneBy({ mobile });
return await this.memberRepository.findOne({
where: { mobile, is_del: 0 },
});
}
/**
@@ -127,7 +135,10 @@ export class CoreMemberService extends BaseService<Member> {
delete updateDto.site_id;
delete updateDto.register_time;
await this.update(memberId, updateDto);
await this.memberRepository.update(
{ member_id: memberId },
{ ...updateDto, update_time: Math.floor(Date.now() / 1000) },
);
}
/**

View File

@@ -16,35 +16,30 @@ export class CloudController {
@Post('build')
@ApiOperation({ summary: '云编译' })
async build(@Body() body: CloudBuildDto) {
const data = await this.service.build(body);
return { code: 200, message: '编译成功', data };
return await this.service.build(body);
}
@Get('buildLog')
@ApiOperation({ summary: '获取云编译日志' })
async getBuildLog(@Query() query: CloudLogDto) {
const data = await this.service.getBuildLog(query);
return { code: 200, message: '获取成功', data };
return await this.service.getBuildLog(query);
}
@Get('buildTask')
@ApiOperation({ summary: '获取云编译任务' })
async getBuildTask() {
const data = await this.service.getBuildTask();
return { code: 200, message: '获取成功', data };
return await this.service.getBuildTask();
}
@Post('cancelBuild')
@ApiOperation({ summary: '取消云编译' })
async cancelBuild() {
const data = await this.service.cancelBuild();
return { code: 200, message: '取消成功', data };
return await this.service.cancelBuild();
}
@Get('buildStatus')
@ApiOperation({ summary: '获取编译状态' })
async getBuildStatus() {
const data = await this.service.getBuildStatus();
return { code: 200, message: '获取成功', data };
return await this.service.getBuildStatus();
}
}

View File

@@ -16,62 +16,54 @@ export class ModuleController {
@Get('page')
@ApiOperation({ summary: '模块分页' })
async page(@Query() query: ModuleQueryDto) {
const data = await this.service.getPage(query);
return { code: 200, message: '获取成功', data };
return await this.service.getPage(query);
}
@Get(':id')
@ApiOperation({ summary: '模块详情' })
@ApiParam({ name: 'id', description: '模块ID' })
async info(@Param('id', ParseIntPipe) id: number) {
const data = await this.service.getInfo(id);
return { code: 200, message: '获取成功', data };
return await this.service.getInfo(id);
}
@Post()
@ApiOperation({ summary: '新增模块' })
async add(@Body() body: CreateModuleDto) {
const data = await this.service.add(body);
return { code: 200, message: '创建成功', data };
return await this.service.add(body);
}
@Put(':id')
@ApiOperation({ summary: '编辑模块' })
@ApiParam({ name: 'id', description: '模块ID' })
async edit(@Param('id', ParseIntPipe) id: number, @Body() body: UpdateModuleDto) {
const data = await this.service.edit(id, body);
return { code: 200, message: '更新成功', data };
return await this.service.edit(id, body);
}
@Delete(':id')
@ApiOperation({ summary: '删除模块' })
@ApiParam({ name: 'id', description: '模块ID' })
async delete(@Param('id', ParseIntPipe) id: number) {
const data = await this.service.delete(id);
return { code: 200, message: '删除成功', data };
return await this.service.delete(id);
}
@Post(':id/install')
@ApiOperation({ summary: '安装模块' })
@ApiParam({ name: 'id', description: '模块ID' })
async install(@Param('id', ParseIntPipe) id: number) {
const data = await this.service.install(id);
return { code: 200, message: '安装成功', data };
return await this.service.install(id);
}
@Post(':id/uninstall')
@ApiOperation({ summary: '卸载模块' })
@ApiParam({ name: 'id', description: '模块ID' })
async uninstall(@Param('id', ParseIntPipe) id: number) {
const data = await this.service.uninstall(id);
return { code: 200, message: '卸载成功', data };
return await this.service.uninstall(id);
}
@Post(':id/upgrade')
@ApiOperation({ summary: '升级模块' })
@ApiParam({ name: 'id', description: '模块ID' })
async upgrade(@Param('id', ParseIntPipe) id: number) {
const data = await this.service.upgrade(id);
return { code: 200, message: '升级成功', data };
return await this.service.upgrade(id);
}
}

View File

@@ -1,4 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Cloud } from './entities/Cloud';
import { Module as NiucloudModuleEntity } from './entities/Module';
import { CloudController } from './controllers/adminapi/CloudController';
import { ModuleController } from './controllers/adminapi/ModuleController';
import { CloudService } from './services/admin/CloudService';
@@ -7,6 +10,7 @@ import { CoreCloudService } from './services/core/CoreCloudService';
import { CoreModuleService } from './services/core/CoreModuleService';
@Module({
imports: [TypeOrmModule.forFeature([Cloud, NiucloudModuleEntity])],
controllers: [CloudController, ModuleController],
providers: [CloudService, ModuleService, CoreCloudService, CoreModuleService],
exports: [CloudService, ModuleService, CoreCloudService, CoreModuleService],

View File

@@ -1,35 +1,46 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
@Entity('sys_notice_sms_log')
export class SmsLog extends BaseEntity {
export class SmsLog {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'mobile', type: 'varchar', length: 11, default: '' })
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ name: 'mobile', type: 'varchar', length: 11, default: '', comment: '手机号码' })
mobile: string;
@Column({ name: 'sms_type', type: 'varchar', length: 32, default: '' })
@Column({ name: 'sms_type', type: 'varchar', length: 32, default: '', comment: '发送关键字(注册、找回密码)' })
sms_type: string;
@Column({ name: 'key', type: 'varchar', length: 32, default: '' })
@Column({ name: 'key', type: 'varchar', length: 32, default: '', comment: '发送关键字(注册、找回密码)' })
key: string;
@Column({ name: 'template_id', type: 'varchar', length: 50, default: '' })
@Column({ name: 'template_id', type: 'varchar', length: 50, default: '', comment: '模板ID' })
template_id: string;
@Column({ name: 'content', type: 'text' })
@Column({ name: 'content', type: 'text', comment: '发送内容' })
content: string;
@Column({ name: 'params', type: 'text' })
params: any;
@Column({ name: 'params', type: 'text', comment: '数据参数' })
params: string;
@Column({ name: 'status', type: 'varchar', length: 32, default: 'sending' })
@Column({ name: 'status', type: 'varchar', length: 32, default: 'sending', comment: '发送状态sending-发送中success-发送成功fail-发送失败' })
status: string;
@Column({ name: 'result', type: 'text', nullable: true })
@Column({ name: 'result', type: 'text', nullable: true, comment: '短信结果' })
result: string;
@Column({ name: 'send_time', type: 'int', default: 0 })
@Column({ name: 'send_time', type: 'int', default: 0, comment: '发送时间' })
send_time: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '创建时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
update_time: number;
@DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
delete_time: number;
}

View File

@@ -1,19 +1,16 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { SmsLog } from '../../entities/SmsLog';
import { ISmsConfig, ISmsSendParams } from '../../interfaces/sms.interface';
import { SmsStatus } from '../../enums/status.enum';
@Injectable()
export class CoreSmsService extends BaseService<SmsLog> {
export class CoreSmsService {
constructor(
@InjectRepository(SmsLog)
private readonly smsLogRepository: Repository<SmsLog>,
) {
super(smsLogRepository);
}
) {}
/**
* 发送短信
@@ -30,7 +27,7 @@ export class CoreSmsService extends BaseService<SmsLog> {
try {
// 创建短信日志
savedLog = await this.create({
savedLog = await this.smsLogRepository.save(this.smsLogRepository.create({
site_id,
mobile,
sms_type: 'default',
@@ -39,15 +36,15 @@ export class CoreSmsService extends BaseService<SmsLog> {
template_id,
params: JSON.stringify(params),
status: SmsStatus.SENDING,
send_time: this.getCurrentTimestamp(),
});
send_time: Math.floor(Date.now() / 1000),
}));
// TODO: 这里应该调用实际的短信服务商API
// 目前先模拟发送成功
const success = true;
// 更新日志状态
await this.update(savedLog.id, {
await this.smsLogRepository.update(savedLog.id, {
status: success ? SmsStatus.SUCCESS : SmsStatus.FAILED,
result: success ? '发送成功' : '发送失败',
});
@@ -56,7 +53,7 @@ export class CoreSmsService extends BaseService<SmsLog> {
} catch (error) {
// 记录失败日志
if (savedLog?.id) {
await this.update(savedLog.id, {
await this.smsLogRepository.update(savedLog.id, {
status: SmsStatus.FAILED,
result: error.message,
});
@@ -85,6 +82,6 @@ export class CoreSmsService extends BaseService<SmsLog> {
*/
async getSmsLogs(site_id: number, where: any = {}): Promise<SmsLog[]> {
const query = { site_id, ...where };
return await this.findMany(query);
return await this.smsLogRepository.find({ where: query });
}
}

View File

@@ -9,6 +9,7 @@
Query,
UseGuards,
Req,
UnauthorizedException,
} from "@nestjs/common";
import {
ApiTags,
@@ -43,19 +44,19 @@ export class PayChannelController {
constructor(private readonly payChannelService: PayChannelService) {}
@Get("list")
@Roles('admin')
@ApiOperation({ summary: "获取支付渠道列表" })
@ApiResponse({ status: 200, description: "获取成功" })
async getChannelList(@Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payChannelService.getChannelList(siteId);
return { code: 200, message: "获取成功", data: result };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return await this.payChannelService.getChannelList(siteId);
}
@Get(":type/:channel")
@Roles('admin')
@ApiOperation({ summary: "获取支付渠道配置" })
@ApiParam({ name: "type", description: "支付方式" })
@ApiParam({ name: "channel", description: "支付渠道" })
@@ -65,16 +66,15 @@ export class PayChannelController {
@Param("channel") channel: string,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payChannelService.getConfig(siteId, type, channel);
return { code: 200, message: "获取成功", data: result };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return await this.payChannelService.getConfig(siteId, type, channel);
}
@Post(":type/:channel")
@Roles('admin')
@ApiOperation({ summary: "设置支付渠道配置" })
@ApiParam({ name: "type", description: "支付方式" })
@ApiParam({ name: "channel", description: "支付渠道" })
@@ -85,16 +85,16 @@ export class PayChannelController {
@Body() createPayChannelDto: CreatePayChannelDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payChannelService.set(siteId, channel, type, createPayChannelDto);
return { code: 200, message: "设置成功", data: result };
} catch (error) {
return { code: 500, message: "设置失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payChannelService.set(siteId, channel, type, createPayChannelDto);
return { success: true, message: '设置成功' };
}
@Put(":type/:channel")
@Roles('admin')
@ApiOperation({ summary: "更新支付渠道配置" })
@ApiParam({ name: "type", description: "支付方式" })
@ApiParam({ name: "channel", description: "支付渠道" })
@@ -105,16 +105,16 @@ export class PayChannelController {
@Body() updatePayChannelDto: UpdatePayChannelDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payChannelService.set(siteId, channel, type, updatePayChannelDto);
return { code: 200, message: "更新成功", data: result };
} catch (error) {
return { code: 500, message: "更新失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payChannelService.set(siteId, channel, type, updatePayChannelDto);
return { success: true, message: '更新成功' };
}
@Delete(":type/:channel")
@Roles('admin')
@ApiOperation({ summary: "删除支付渠道配置" })
@ApiParam({ name: "type", description: "支付方式" })
@ApiParam({ name: "channel", description: "支付渠道" })
@@ -124,16 +124,16 @@ export class PayChannelController {
@Param("channel") channel: string,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payChannelService.delete(siteId, type, channel);
return { code: 200, message: "删除成功", data: result };
} catch (error) {
return { code: 500, message: "删除失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payChannelService.delete(siteId, type, channel);
return { success: true, message: '删除成功' };
}
@Put(":type/:channel/status")
@Roles('admin')
@ApiOperation({ summary: "更新支付渠道状态" })
@ApiParam({ name: "type", description: "支付方式" })
@ApiParam({ name: "channel", description: "支付渠道" })
@@ -144,12 +144,11 @@ export class PayChannelController {
@Body() body: { status: number },
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payChannelService.updateStatus(siteId, type, channel, body.status);
return { code: 200, message: "更新成功", data: result };
} catch (error) {
return { code: 500, message: "更新失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payChannelService.updateStatus(siteId, type, channel, body.status);
return { success: true, message: '更新成功' };
}
}

View File

@@ -7,6 +7,8 @@
Query,
UseGuards,
Req,
UnauthorizedException,
NotFoundException,
} from "@nestjs/common";
import {
ApiTags,
@@ -42,77 +44,76 @@ export class PayController {
constructor(private readonly payService: PayService) {}
@Get("audit/page")
@Roles('admin')
@ApiOperation({ summary: "获取待审核支付记录" })
@ApiResponse({ status: 200, description: "获取成功" })
async getAuditPage(@Query() query: PayAuditQueryDto, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payService.getAuditPage(siteId, query);
return { code: 200, message: "获取成功", data: result };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
return await this.payService.getAuditPage(siteId, query);
}
@Get(":id")
@Roles('admin')
@ApiOperation({ summary: "获取支付详情" })
@ApiParam({ name: "id", description: "支付ID" })
@ApiResponse({ status: 200, description: "获取成功" })
async getDetail(@Param("id") id: number, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payService.getDetail(siteId, id);
if (!result) {
return { code: 404, message: "支付记录不存在" };
}
return { code: 200, message: "获取成功", data: result };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payService.getDetail(siteId, id);
if (!result) {
throw new NotFoundException('支付记录不存在');
}
return result;
}
@Post("pass")
@Roles('admin')
@ApiOperation({ summary: "支付审核通过" })
@ApiResponse({ status: 200, description: "审核成功" })
async pass(@Body() payPassDto: PayPassDto, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payService.pass(siteId, payPassDto.outTradeNo);
if (!result) {
return { code: 404, message: "支付记录不存在" };
}
return { code: 200, message: "审核通过成功" };
} catch (error) {
return { code: 500, message: "审核失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payService.pass(siteId, payPassDto.outTradeNo);
if (!result) {
throw new NotFoundException('支付记录不存在');
}
return { success: true, message: '审核通过成功' };
}
@Post("refuse")
@Roles('admin')
@ApiOperation({ summary: "支付审核拒绝" })
@ApiResponse({ status: 200, description: "审核成功" })
async refuse(@Body() payRefuseDto: PayRefuseDto, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payService.refuse(siteId, payRefuseDto.outTradeNo, payRefuseDto.reason);
if (!result) {
return { code: 404, message: "支付记录不存在" };
}
return { code: 200, message: "审核拒绝成功" };
} catch (error) {
return { code: 500, message: "审核失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payService.refuse(siteId, payRefuseDto.outTradeNo, payRefuseDto.reason);
if (!result) {
throw new NotFoundException('支付记录不存在');
}
return { success: true, message: '审核拒绝成功' };
}
@Get("count")
@Roles('admin')
@ApiOperation({ summary: "统计支付数据" })
@ApiResponse({ status: 200, description: "获取成功" })
async payCount(@Query() query: any, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payService.payCount(siteId, query);
return { code: 200, message: "获取成功", data: { count: result } };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const result = await this.payService.payCount(siteId, query);
return { count: result };
}
}

View File

@@ -1,100 +0,0 @@
import { Body, Controller, Post, Get, Query, Req, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiTags, ApiResponse } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { CreatePayTemplateDto } from '../../dto/admin/CreatePayTemplateDto';
import { UpdatePayTemplateDto } from '../../dto/admin/UpdatePayTemplateDto';
import { PayTemplateService } from '../../services/admin/PayTemplateService';
interface AuthenticatedRequest extends Request {
user?: {
siteId?: number;
uid?: number;
};
}
/**
* 支付模板控制器 - Admin层
* 对应PHP: 支付模板相关控制器
*/
@ApiTags('支付模板')
@Controller('adminapi/pay/template')
@UseGuards(JwtAuthGuard, RolesGuard)
export class PayTemplateController {
constructor(private readonly payTemplateService: PayTemplateService) {}
@Post('create')
@ApiOperation({ summary: '创建支付模板' })
@ApiResponse({ status: 200, description: '创建成功' })
async create(@Body() createPayTemplateDto: CreatePayTemplateDto, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payTemplateService.create(siteId, createPayTemplateDto);
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 500, message: '创建失败', error: error.message };
}
}
@Post('update')
@ApiOperation({ summary: '更新支付模板' })
@ApiResponse({ status: 200, description: '更新成功' })
async update(@Body() updatePayTemplateDto: UpdatePayTemplateDto, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const id = updatePayTemplateDto['id'];
if (!id) {
return { code: 400, message: '缺少ID参数' };
}
const result = await this.payTemplateService.update(siteId, id, updatePayTemplateDto);
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 500, message: '更新失败', error: error.message };
}
}
@Get('detail')
@ApiOperation({ summary: '获取支付模板详情' })
@ApiResponse({ status: 200, description: '获取成功' })
async detail(@Query('id') id: number, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payTemplateService.getDetail(siteId, id);
if (!result) {
return { code: 404, message: '支付模板不存在' };
}
return { code: 200, message: '获取成功', data: result };
} catch (error) {
return { code: 500, message: '获取失败', error: error.message };
}
}
@Get('list')
@ApiOperation({ summary: '获取支付模板列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async list(@Query() query: any, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payTemplateService.getList(siteId, query);
return { code: 200, message: '获取成功', data: result };
} catch (error) {
return { code: 500, message: '获取失败', error: error.message };
}
}
@Post('delete')
@ApiOperation({ summary: '删除支付模板' })
@ApiResponse({ status: 200, description: '删除成功' })
async delete(@Body('id') id: number, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.payTemplateService.delete(siteId, id);
if (!result) {
return { code: 404, message: '支付模板不存在' };
}
return { code: 200, message: '删除成功' };
} catch (error) {
return { code: 500, message: '删除失败', error: error.message };
}
}
}

View File

@@ -8,6 +8,7 @@ import {
Query,
UseGuards,
} from '@nestjs/common';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { PayRefundService } from '../../services/admin/PayRefundService';
@@ -21,6 +22,7 @@ export class PayRefundController {
* 退款列表
*/
@Get('lists')
@Roles('admin')
async lists(@Query() query: any) {
return this.payRefundService.getPage(query);
}
@@ -29,6 +31,7 @@ export class PayRefundController {
* 退款信息
*/
@Get('info/:refund_id')
@Roles('admin')
async info(@Param('refund_id') refund_id: string) {
return this.payRefundService.getInfo(parseInt(refund_id));
}
@@ -37,6 +40,7 @@ export class PayRefundController {
* 创建退款
*/
@Post('create')
@Roles('admin')
async create(@Body() data: {
pay_id: number;
refund_amount: number;
@@ -51,6 +55,7 @@ export class PayRefundController {
* 处理退款
*/
@Post('process/:refund_id')
@Roles('admin')
async process(@Param('refund_id') refund_id: string) {
return this.payRefundService.process(parseInt(refund_id));
}
@@ -59,6 +64,7 @@ export class PayRefundController {
* 取消退款
*/
@Post('cancel/:refund_id')
@Roles('admin')
async cancel(@Param('refund_id') refund_id: string) {
return this.payRefundService.cancel(parseInt(refund_id));
}
@@ -67,6 +73,7 @@ export class PayRefundController {
* 获取退款状态
*/
@Get('status/:refund_id')
@Roles('admin')
async getStatus(@Param('refund_id') refund_id: string) {
return this.payRefundService.getStatus(parseInt(refund_id));
}
@@ -75,6 +82,7 @@ export class PayRefundController {
* 获取退款统计
*/
@Get('statistics')
@Roles('admin')
async getStatistics(@Query() query: any) {
return this.payRefundService.getStatistics(query);
}

View File

@@ -8,6 +8,7 @@ import {
Query,
UseGuards,
} from '@nestjs/common';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { TransferService } from '../../services/admin/TransferService';
@@ -21,6 +22,7 @@ export class TransferController {
* 转账列表
*/
@Get('lists')
@Roles('admin')
async lists(@Query() query: any) {
return this.transferService.getPage(query);
}
@@ -29,6 +31,7 @@ export class TransferController {
* 转账信息
*/
@Get('info/:transfer_id')
@Roles('admin')
async info(@Param('transfer_id') transfer_id: string) {
return this.transferService.getInfo(parseInt(transfer_id));
}
@@ -37,6 +40,7 @@ export class TransferController {
* 创建转账
*/
@Post('create')
@Roles('admin')
async create(@Body() data: {
transfer_type: string;
transfer_amount: number;
@@ -52,6 +56,7 @@ export class TransferController {
* 处理转账
*/
@Post('process/:transfer_id')
@Roles('admin')
async process(@Param('transfer_id') transfer_id: string) {
return this.transferService.process(parseInt(transfer_id));
}
@@ -60,6 +65,7 @@ export class TransferController {
* 取消转账
*/
@Post('cancel/:transfer_id')
@Roles('admin')
async cancel(@Param('transfer_id') transfer_id: string) {
return this.transferService.cancel(parseInt(transfer_id));
}
@@ -68,6 +74,7 @@ export class TransferController {
* 获取转账状态
*/
@Get('status/:transfer_id')
@Roles('admin')
async getStatus(@Param('transfer_id') transfer_id: string) {
return this.transferService.getStatus(parseInt(transfer_id));
}
@@ -76,6 +83,7 @@ export class TransferController {
* 获取转账统计
*/
@Get('statistics')
@Roles('admin')
async getStatistics(@Query() query: any) {
return this.transferService.getStatistics(query);
}

View File

@@ -1,73 +0,0 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsOptional, IsEnum, ValidateIf } from 'class-validator';
import { Transform } from 'class-transformer';
/**
* 支付方式枚举
* 对应PHP: app\dict\pay\PayDict
*/
export enum PayType {
WECHATPAY = 'wechatpay',
ALIPAY = 'alipay',
}
/**
* 创建支付模板DTO
* 对应PHP: app\validate\pay\PayTemplate 的 add 场景
*/
export class CreatePayTemplateDto {
@ApiProperty({ description: '模板名称', example: '微信支付' })
@IsString()
name: string;
@ApiProperty({ description: '支付方式', enum: PayType, example: PayType.WECHATPAY })
@IsEnum(PayType, { message: '不存在的支付方式' })
type: PayType;
// 支付宝相关字段 - 仅当type为ALIPAY时必填
@ApiPropertyOptional({ description: '支付宝AppID', example: '2021000000000000' })
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝AppID必填' })
app_id?: string;
@ApiPropertyOptional({ description: '支付宝应用私钥证书' })
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝应用私钥证书必填' })
app_secret_cert?: string;
@ApiPropertyOptional({ description: '支付宝应用公钥证书路径' })
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝应用公钥证书路径必填' })
app_public_cert_path?: string;
@ApiPropertyOptional({ description: '支付宝公钥证书路径' })
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝公钥证书路径必填' })
alipay_public_cert_path?: string;
@ApiPropertyOptional({ description: '支付宝根证书路径' })
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝根证书路径必填' })
alipay_root_cert_path?: string;
// 微信支付相关字段 - 仅当type为WECHATPAY时必填
@ApiPropertyOptional({ description: '微信商户号', example: '1900000109' })
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户号必填' })
mch_id?: string;
@ApiPropertyOptional({ description: '微信商户API密钥' })
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户API密钥必填' })
mch_secret_key?: string;
@ApiPropertyOptional({ description: '微信商户私钥证书' })
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户私钥证书必填' })
mch_secret_cert?: string;
@ApiPropertyOptional({ description: '微信商户公钥证书路径' })
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户公钥证书路径必填' })
mch_public_cert_path?: string;
}

View File

@@ -1,78 +0,0 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsOptional, IsEnum, ValidateIf, IsNumber } from 'class-validator';
import { Transform } from 'class-transformer';
import { PayType } from './CreatePayTemplateDto';
/**
* 更新支付模板DTO
* 对应PHP: app\validate\pay\PayTemplate 的 edit 场景
*/
export class UpdatePayTemplateDto {
@ApiProperty({ description: '支付模板ID', example: 1 })
@IsNumber({}, { message: '支付模板ID必须是数字' })
id: number;
@ApiProperty({ description: '模板名称', example: '微信支付' })
@IsString()
name: string;
@ApiPropertyOptional({ description: '支付方式', enum: PayType, example: PayType.WECHATPAY })
@IsOptional()
@IsEnum(PayType, { message: '不存在的支付方式' })
type?: PayType;
// 支付宝相关字段 - 仅当type为ALIPAY时必填
@ApiPropertyOptional({ description: '支付宝AppID', example: '2021000000000000' })
@IsOptional()
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝AppID必填' })
app_id?: string;
@ApiPropertyOptional({ description: '支付宝应用私钥证书' })
@IsOptional()
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝应用私钥证书必填' })
app_secret_cert?: string;
@ApiPropertyOptional({ description: '支付宝应用公钥证书路径' })
@IsOptional()
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝应用公钥证书路径必填' })
app_public_cert_path?: string;
@ApiPropertyOptional({ description: '支付宝公钥证书路径' })
@IsOptional()
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝公钥证书路径必填' })
alipay_public_cert_path?: string;
@ApiPropertyOptional({ description: '支付宝根证书路径' })
@IsOptional()
@ValidateIf(o => o.type === PayType.ALIPAY)
@IsString({ message: '支付宝根证书路径必填' })
alipay_root_cert_path?: string;
// 微信支付相关字段 - 仅当type为WECHATPAY时必填
@ApiPropertyOptional({ description: '微信商户号', example: '1900000109' })
@IsOptional()
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户号必填' })
mch_id?: string;
@ApiPropertyOptional({ description: '微信商户API密钥' })
@IsOptional()
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户API密钥必填' })
mch_secret_key?: string;
@ApiPropertyOptional({ description: '微信商户私钥证书' })
@IsOptional()
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户私钥证书必填' })
mch_secret_cert?: string;
@ApiPropertyOptional({ description: '微信商户公钥证书路径' })
@IsOptional()
@ValidateIf(o => o.type === PayType.WECHATPAY)
@IsString({ message: '微信商户公钥证书路径必填' })
mch_public_cert_path?: string;
}

View File

@@ -1,2 +0,0 @@
export * from './CreatePayTemplateDto';
export * from './UpdatePayTemplateDto';

View File

@@ -1,60 +1,71 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
import { BaseEntity } from "../../../core/base/BaseEntity";
/**
* 支付实体
* 对应PHP: app\model\pay\Pay
*/
@Entity("pay")
export class Pay extends BaseEntity {
export class Pay {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: "site_id", type: "int", comment: "站点ID" })
siteId: number;
@Column({ name: "site_id", type: "int", default: 0, comment: "站点id" })
site_id: number;
@Column({ name: "out_trade_no", length: 100, comment: "商户订单号" })
outTradeNo: string;
@Column({ name: "main_id", type: "int", default: 0, comment: "支付会员id" })
main_id: number;
@Column({ name: "trade_type", length: 50, comment: "交易类型" })
tradeType: string;
@Column({ name: "from_main_id", type: "int", default: 0, comment: "发起支付会员id" })
from_main_id: number;
@Column({ name: "trade_id", type: "int", default: 0, comment: "交易ID" })
tradeId: number;
@Column({ name: "out_trade_no", type: "varchar", length: 255, default: "", comment: "支付流水号" })
out_trade_no: string;
@Column({ name: "trade_no", length: 100, default: "", comment: "交易流水号" })
tradeNo: string;
@Column({ name: "trade_type", type: "varchar", length: 255, default: "", comment: "业务类型" })
trade_type: string;
@Column({ name: "body", length: 500, comment: "商品描述" })
@Column({ name: "trade_id", type: "int", default: 0, comment: "业务id" })
trade_id: number;
@Column({ name: "trade_no", type: "varchar", length: 255, default: "", comment: "交易单号" })
trade_no: string;
@Column({ name: "body", type: "varchar", length: 1000, default: "", comment: "支付主体" })
body: string;
@Column({ name: "money", type: "decimal", precision: 10, scale: 2, comment: "支付金额" })
@Column({ name: "money", type: "decimal", precision: 10, scale: 2, default: 0.00, comment: "支付金额" })
money: number;
@Column({ name: "voucher", length: 500, default: "", comment: "支付凭证" })
@Column({ name: "voucher", type: "varchar", length: 255, default: "", comment: "支付票据" })
voucher: string;
@Column({ name: "status", type: "tinyint", default: 0, comment: "支付状态 0待支付 1支付 2已关闭 3已退款" })
@Column({ name: "status", type: "int", default: 0, comment: "支付状态0.待支付 1. 支付 2. 已支付 -1已取消" })
status: number;
@Column({ name: "type", length: 50, comment: "支付方式" })
@Column({ name: "json", type: "varchar", length: 255, default: "", comment: "支付扩展用支付信息" })
json: string;
@CreateDateColumn({ name: "create_time", type: "int", default: 0, comment: "创建时间" })
create_time: number;
@Column({ name: "pay_time", type: "int", default: 0, comment: "支付时间" })
pay_time: number;
@Column({ name: "type", type: "varchar", length: 255, default: "", comment: "支付方式" })
type: string;
@Column({ name: "channel", length: 50, comment: "支付渠道" })
@Column({ name: "channel", type: "varchar", length: 50, default: "", comment: "支付渠道" })
channel: string;
@Column({ name: "fail_reason", length: 500, default: "", comment: "失败原因" })
failReason: string;
@Column({ name: "fail_reason", type: "varchar", length: 255, default: "", comment: "失败原因" })
fail_reason: string;
@Column({ name: "allow_type", type: "json", nullable: true, comment: "允许的支付方式" })
allowType: string[];
@Column({ name: "close_time", type: "int", default: 0, comment: "关闭时间" })
close_time: number;
@Column({ name: "pay_time", type: "timestamp", nullable: true, comment: "支付时间" })
payTime: Date;
@Column({ name: "is_del", type: "tinyint", default: 0, comment: "是否删除" })
is_del: number;
@Column({ name: "close_time", type: "timestamp", nullable: true, comment: "关闭时间" })
closeTime: Date;
@CreateDateColumn({ name: "create_time", comment: "创建时间" })
createTime: Date;
@UpdateDateColumn({ name: "update_time", type: "int", default: 0, comment: "更新时间" })
update_time: number;
}

View File

@@ -1,50 +0,0 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/**
* 支付模板实体
* 对应PHP: 支付模板相关数据表
*/
@Entity('pay_template')
export class PayTemplate extends BaseEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ name: 'name', type: 'varchar', length: 50, comment: '模板名称' })
name: string;
@Column({ name: 'type', type: 'varchar', length: 50, comment: '支付方式' })
type: string;
// 支付宝相关字段
@Column({ name: 'app_id', type: 'varchar', length: 255, nullable: true, comment: '支付宝AppID' })
app_id: string | null;
@Column({ name: 'app_secret_cert', type: 'text', nullable: true, comment: '支付宝应用私钥证书' })
app_secret_cert: string | null;
@Column({ name: 'app_public_cert_path', type: 'varchar', length: 255, nullable: true, comment: '支付宝应用公钥证书路径' })
app_public_cert_path: string | null;
@Column({ name: 'alipay_public_cert_path', type: 'varchar', length: 255, nullable: true, comment: '支付宝公钥证书路径' })
alipay_public_cert_path: string | null;
@Column({ name: 'alipay_root_cert_path', type: 'varchar', length: 255, nullable: true, comment: '支付宝根证书路径' })
alipay_root_cert_path: string | null;
// 微信支付相关字段
@Column({ name: 'mch_id', type: 'varchar', length: 255, nullable: true, comment: '微信商户号' })
mch_id: string | null;
@Column({ name: 'mch_secret_key', type: 'varchar', length: 255, nullable: true, comment: '微信商户API密钥' })
mch_secret_key: string | null;
@Column({ name: 'mch_secret_cert', type: 'text', nullable: true, comment: '微信商户私钥证书' })
mch_secret_cert: string | null;
@Column({ name: 'mch_public_cert_path', type: 'varchar', length: 255, nullable: true, comment: '微信商户公钥证书路径' })
mch_public_cert_path: string | null;
// site_id/create_time/update_time 由 BaseEntity 提供
}

View File

@@ -1,8 +1,7 @@
import { Module } from "@nestjs/common";
import { Module, forwardRef } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Pay } from "./entities/Pay";
import { PayChannel } from "./entities/PayChannel";
import { PayTemplate } from "./entities/PayTemplate";
import { PayRefund } from "./entities/PayRefund";
import { PayTransfer } from "./entities/PayTransfer";
@@ -15,7 +14,6 @@ import { CorePayTransferService } from "./services/core/CorePayTransferService";
// Admin Services
import { PayService } from "./services/admin/PayService";
import { PayChannelService } from "./services/admin/PayChannelService";
import { PayTemplateService } from "./services/admin/PayTemplateService";
import { PayApiService } from "./services/api/PayApiService";
import { TransferApiService } from "./services/api/TransferApiService";
import { PayRefundService } from "./services/admin/PayRefundService";
@@ -24,12 +22,13 @@ import { TransferService } from "./services/admin/TransferService";
// Admin Controllers
import { PayController } from "./controllers/admin/PayController";
import { PayChannelController } from "./controllers/admin/PayChannelController";
import { PayTemplateController } from "./controllers/admin/PayTemplateController";
import { PayApiController } from "./controllers/api/PayApiController";
import { TransferApiController } from "./controllers/api/TransferApiController";
import { PayRefundController } from "./controllers/adminapi/PayRefundController";
import { TransferController } from "./controllers/adminapi/TransferController";
import { JobsModule } from "../../common/jobs/jobs.module";
import { VendorModule } from "../../vendor";
import { PaymentAdapterRegistry } from "../../vendor/pay/providers/payment.provider";
import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
/**
@@ -38,8 +37,9 @@ import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
*/
@Module({
imports: [
TypeOrmModule.forFeature([Pay, PayChannel, PayTemplate, PayRefund, PayTransfer]),
JobsModule,
TypeOrmModule.forFeature([Pay, PayChannel, PayRefund, PayTransfer]),
forwardRef(() => JobsModule),
VendorModule,
],
providers: [
// Core Services
@@ -51,18 +51,17 @@ import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
// Admin Services
PayService,
PayChannelService,
PayTemplateService,
PayRefundService,
TransferService,
PayApiService,
TransferApiService,
PaymentEventHandlers,
PaymentAdapterRegistry,
],
controllers: [
// Admin Controllers
PayController,
PayChannelController,
PayTemplateController,
PayApiController,
TransferApiController,
PayRefundController,
@@ -84,6 +83,7 @@ import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
// Api Services
PayApiService,
TransferApiService,
PaymentAdapterRegistry,
],
})
export class PayModule {}

View File

@@ -1,127 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PayTemplate } from '../../entities/PayTemplate';
import { CreatePayTemplateDto, PayType } from '../../dto/admin/CreatePayTemplateDto';
import { UpdatePayTemplateDto } from '../../dto/admin/UpdatePayTemplateDto';
/**
* 支付模板服务 - Admin层
* 对应PHP: 支付模板相关服务
*/
@Injectable()
export class PayTemplateService {
constructor(
@InjectRepository(PayTemplate)
private readonly payTemplateRepository: Repository<PayTemplate>,
) {}
/**
* 创建支付模板
* @param siteId 站点ID
* @param createPayTemplateDto 创建支付模板DTO
* @returns 创建的支付模板
*/
async create(siteId: number, createPayTemplateDto: CreatePayTemplateDto) {
const payTemplate = this.payTemplateRepository.create({
site_id: siteId,
...createPayTemplateDto,
create_time: Math.floor(Date.now() / 1000),
});
return await this.payTemplateRepository.save(payTemplate);
}
/**
* 更新支付模板
* @param siteId 站点ID
* @param id 支付模板ID
* @param updatePayTemplateDto 更新支付模板DTO
* @returns 更新后的支付模板
*/
async update(siteId: number, id: number, updatePayTemplateDto: UpdatePayTemplateDto) {
const payTemplate = await this.payTemplateRepository.findOne({
where: { id, site_id: siteId },
});
if (!payTemplate) {
throw new Error('支付模板不存在');
}
// 更新字段
Object.assign(payTemplate, updatePayTemplateDto, {
update_time: Math.floor(Date.now() / 1000),
});
return await this.payTemplateRepository.save(payTemplate);
}
/**
* 获取支付模板详情
* @param siteId 站点ID
* @param id 支付模板ID
* @returns 支付模板详情
*/
async getDetail(siteId: number, id: number) {
return await this.payTemplateRepository.findOne({
where: { id, site_id: siteId },
});
}
/**
* 获取支付模板列表
* @param siteId 站点ID
* @param query 查询参数
* @returns 支付模板列表
*/
async getList(siteId: number, query: any) {
const queryBuilder = this.payTemplateRepository.createQueryBuilder('pay_template')
.where('pay_template.site_id = :siteId', { siteId });
// 按名称搜索
if (query.name) {
queryBuilder.andWhere('pay_template.name LIKE :name', { name: `%${query.name}%` });
}
// 按支付方式筛选
if (query.type) {
queryBuilder.andWhere('pay_template.type = :type', { type: query.type });
}
// 分页
const page = query.page || 1;
const pageSize = query.page_size || 10;
const skip = (page - 1) * pageSize;
queryBuilder.orderBy('pay_template.create_time', 'DESC')
.skip(skip)
.take(pageSize);
const [list, total] = await queryBuilder.getManyAndCount();
return {
list,
page_info: {
page,
page_size: pageSize,
count: total,
page_count: Math.ceil(total / pageSize),
},
};
}
/**
* 删除支付模板
* @param siteId 站点ID
* @param id 支付模板ID
* @returns 是否成功
*/
async delete(siteId: number, id: number) {
const result = await this.payTemplateRepository.delete({
id,
site_id: siteId,
});
return (result.affected || 0) > 0;
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable } from "@nestjs/common";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { CorePayService } from "../core/CorePayService";
import { CorePayChannelService } from "../core/CorePayChannelService";
import { CreatePayDto, RefundDto } from "../../dto/PayDto";
@@ -83,17 +83,17 @@ export class PayApiService {
return existed;
}
const created = await this.corePayService.add({
siteId,
outTradeNo: dto.outTradeNo,
tradeType: dto.tradeType,
tradeId: dto.tradeId,
site_id: siteId,
out_trade_no: dto.outTradeNo,
trade_type: dto.tradeType,
trade_id: dto.tradeId,
body: dto.body,
money: dto.money as any,
voucher: dto.voucher || "",
status: 0,
type: dto.type,
channel: dto.channel,
allowType: dto.allowType || undefined,
// allowType: dto.allowType || undefined, // 字段不存在,注释掉
});
const gatewayResp = await adapter.create(config, {
outTradeNo: dto.outTradeNo,
@@ -111,7 +111,10 @@ export class PayApiService {
const channel = req.params?.channel || req.query?.channel || req.body?.channel;
const outTradeNoFromReq = req.query?.outTradeNo || req.body?.out_trade_no || req.body?.outTradeNo;
const payUnsafe = outTradeNoFromReq ? await this.corePayService.findByOutTradeNoUnsafe(outTradeNoFromReq) : null;
const siteId = payUnsafe?.siteId || req.user?.siteId || 0;
const siteId = payUnsafe?.site_id || req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('Missing site_id');
}
const { adapter, config } = await this.paymentRegistry.resolve(siteId, type, channel);
const result = await adapter.notify(config, {
headers: req.headers,
@@ -122,7 +125,7 @@ export class PayApiService {
await this.corePayService.updateStatus(siteId, result.outTradeNo, 1, result.tradeNo);
const pay = await this.corePayService.findByOutTradeNo(siteId, result.outTradeNo);
if (pay) {
await this.domainEventService.publishPaymentEvent('succeeded', String(pay.id), String(siteId), { outTradeNo: pay.outTradeNo, tradeNo: pay.tradeNo });
await this.domainEventService.publishPaymentEvent('succeeded', String(pay.id), String(siteId), { outTradeNo: pay.out_trade_no, tradeNo: pay.trade_no });
}
} else if (result.status === 'CLOSED') {
await this.corePayService.updateStatus(siteId, result.outTradeNo, 2);
@@ -134,11 +137,11 @@ export class PayApiService {
const pay = await this.corePayService.findByOutTradeNo(siteId, outTradeNo);
if (!pay) return null;
return {
outTradeNo: pay.outTradeNo,
outTradeNo: pay.out_trade_no,
status: pay.status,
tradeNo: pay.tradeNo,
payTime: pay.payTime,
closeTime: pay.closeTime,
tradeNo: pay.trade_no,
payTime: pay.pay_time,
closeTime: pay.close_time,
};
}
@@ -159,7 +162,7 @@ export class PayApiService {
refundMoney: body.refundMoney,
reason: body.reason,
});
await this.domainEventService.publishPaymentEvent('refunded', String(pay.id), String(siteId), { outTradeNo: pay.outTradeNo, refundNo: body.refundNo, refundMoney: body.refundMoney });
await this.domainEventService.publishPaymentEvent('refunded', String(pay.id), String(siteId), { outTradeNo: pay.out_trade_no, refundNo: body.refundNo, refundMoney: body.refundMoney });
return result;
}
}

View File

@@ -1,7 +1,6 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository, Like } from "typeorm";
import { BaseService } from "../../../../core/base/BaseService";
import { Pay } from "../../entities/Pay";
/**
@@ -9,13 +8,11 @@ import { Pay } from "../../entities/Pay";
* 对应PHP: app\service\core\pay\CorePayService
*/
@Injectable()
export class CorePayService extends BaseService<Pay> {
export class CorePayService {
constructor(
@InjectRepository(Pay)
private readonly payRepository: Repository<Pay>,
) {
super(payRepository);
}
) {}
/**
* 分页查询支付记录
@@ -29,24 +26,24 @@ export class CorePayService extends BaseService<Pay> {
.where("pay.site_id = :siteId", { siteId })
.select([
"pay.id",
"pay.outTradeNo",
"pay.tradeType",
"pay.tradeId",
"pay.tradeNo",
"pay.out_trade_no",
"pay.trade_type",
"pay.trade_id",
"pay.trade_no",
"pay.body",
"pay.money",
"pay.voucher",
"pay.status",
"pay.type",
"pay.channel",
"pay.failReason",
"pay.payTime",
"pay.closeTime",
"pay.createTime",
"pay.fail_reason",
"pay.pay_time",
"pay.close_time",
"pay.create_time",
]);
if (where.outTradeNo) {
queryBuilder.andWhere("pay.outTradeNo LIKE :outTradeNo", {
queryBuilder.andWhere("pay.out_trade_no LIKE :outTradeNo", {
outTradeNo: `%${where.outTradeNo}%`,
});
}
@@ -94,7 +91,7 @@ export class CorePayService extends BaseService<Pay> {
.andWhere("pay.type = :type", { type: "offline" })
.select([
"pay.id",
"pay.outTradeNo",
"pay.out_trade_no",
"pay.type",
"pay.money",
"pay.body",
@@ -106,7 +103,7 @@ export class CorePayService extends BaseService<Pay> {
]);
if (where.outTradeNo) {
queryBuilder.andWhere("pay.outTradeNo LIKE :outTradeNo", {
queryBuilder.andWhere("pay.out_trade_no LIKE :outTradeNo", {
outTradeNo: `%${where.outTradeNo}%`,
});
}
@@ -141,23 +138,23 @@ export class CorePayService extends BaseService<Pay> {
*/
async getDetail(siteId: number, id: number) {
return await this.payRepository.findOne({
where: { siteId, id },
where: { site_id: siteId, id },
select: [
"id",
"outTradeNo",
"tradeType",
"tradeId",
"tradeNo",
"out_trade_no",
"trade_type",
"trade_id",
"trade_no",
"body",
"money",
"voucher",
"status",
"type",
"channel",
"failReason",
"payTime",
"closeTime",
"createTime",
"fail_reason",
"pay_time",
"close_time",
"create_time",
],
});
}
@@ -199,11 +196,11 @@ export class CorePayService extends BaseService<Pay> {
updateData.tradeNo = tradeNo;
}
} else if (status === 2) {
updateData.closeTime = new Date();
updateData.close_time = new Date();
}
const result = await this.payRepository.update(
{ siteId, outTradeNo },
{ site_id: siteId, out_trade_no: outTradeNo },
updateData,
);
return (result.affected || 0) > 0;
@@ -228,11 +225,11 @@ export class CorePayService extends BaseService<Pay> {
*/
async refuse(siteId: number, outTradeNo: string, reason: string): Promise<boolean> {
const result = await this.payRepository.update(
{ siteId, outTradeNo },
{ site_id: siteId, out_trade_no: outTradeNo },
{
status: 2,
failReason: reason,
closeTime: new Date(),
fail_reason: reason,
close_time: Math.floor(Date.now() / 1000),
},
);
return (result.affected || 0) > 0;
@@ -272,7 +269,7 @@ export class CorePayService extends BaseService<Pay> {
*/
async findByOutTradeNo(siteId: number, outTradeNo: string) {
return await this.payRepository.findOne({
where: { siteId, outTradeNo },
where: { site_id: siteId, out_trade_no: outTradeNo },
});
}
@@ -281,7 +278,7 @@ export class CorePayService extends BaseService<Pay> {
*/
async findByOutTradeNoUnsafe(outTradeNo: string) {
return await this.payRepository.findOne({
where: { outTradeNo },
where: { out_trade_no: outTradeNo },
});
}
@@ -292,7 +289,7 @@ export class CorePayService extends BaseService<Pay> {
const result = await this.payRepository
.createQueryBuilder()
.update(Pay)
.set({ status: 2, closeTime: () => 'CURRENT_TIMESTAMP' })
.set({ status: 2, close_time: () => 'CURRENT_TIMESTAMP' })
.where('status = :status', { status: 0 })
.andWhere('create_time < :before', { before })
.execute();
@@ -381,7 +378,7 @@ export class CorePayService extends BaseService<Pay> {
.where('pay.site_id = :siteId', { siteId });
if (keyword) {
query.andWhere('pay.outTradeNo LIKE :keyword', { keyword: `%${keyword}%` });
query.andWhere('pay.out_trade_no LIKE :keyword', { keyword: `%${keyword}%` });
}
if (status !== '') {
@@ -493,10 +490,10 @@ export class CorePayService extends BaseService<Pay> {
*/
async manualComplete(siteId: number, outTradeNo: string) {
const result = await this.payRepository.update(
{ site_id: siteId, outTradeNo },
{ site_id: siteId, out_trade_no: outTradeNo },
{
status: 1,
payTime: new Date(),
pay_time: Math.floor(Date.now() / 1000),
update_time: Math.floor(Date.now() / 1000),
}
);
@@ -512,10 +509,10 @@ export class CorePayService extends BaseService<Pay> {
*/
async cancel(siteId: number, outTradeNo: string) {
const result = await this.payRepository.update(
{ site_id: siteId, outTradeNo },
{ site_id: siteId, out_trade_no: outTradeNo },
{
status: 2,
closeTime: new Date(),
close_time: Math.floor(Date.now() / 1000),
update_time: Math.floor(Date.now() / 1000),
}
);

View File

@@ -1,11 +1,13 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('sys_role')
export class SysRole extends BaseEntity {
export class SysRole {
@PrimaryGeneratedColumn({ name: 'role_id' })
role_id: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({
name: 'role_name',
type: 'varchar',
@@ -19,7 +21,7 @@ export class SysRole extends BaseEntity {
name: 'rules',
type: 'text',
nullable: true,
comment: '角色权限(菜单键数组JSON)',
comment: '角色权限(menus_id)',
})
rules: string;
@@ -27,10 +29,16 @@ export class SysRole extends BaseEntity {
name: 'status',
type: 'tinyint',
default: 1,
comment: '状态:0=禁用,1=启用',
comment: '状态',
})
status: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '添加时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '最后修改时间' })
update_time: number;
// 业务逻辑方法 - <20>?PHP 项目保持一<E68C81>?
getStatusText(): string {
const statusMap: { [key: number]: string } = { 0: '禁用', 1: '正常' };

View File

@@ -1,31 +1,29 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { SysRole } from '../../entities/SysRole';
@Injectable()
export class CoreRoleService extends BaseService<SysRole> {
export class CoreRoleService {
constructor(
@InjectRepository(SysRole)
private sysRoleRepository: Repository<SysRole>,
) {
super(sysRoleRepository);
}
) {}
async createRole(roleData: Partial<SysRole>): Promise<SysRole> {
return await this.create(roleData);
const role = this.sysRoleRepository.create(roleData);
return await this.sysRoleRepository.save(role);
}
async getRoleById(role_id: number): Promise<SysRole | null> {
return await this.findOne(role_id);
return await this.sysRoleRepository.findOne({ where: { role_id } });
}
async getRoleByName(
role_name: string,
site_id: number,
): Promise<SysRole | null> {
return await this.findOneBy({ role_name, site_id });
return await this.sysRoleRepository.findOne({ where: { role_name, site_id } });
}
async updateRole(
@@ -37,7 +35,7 @@ export class CoreRoleService extends BaseService<SysRole> {
throw new NotFoundException('角色不存在');
}
await this.update(role_id, updateData);
await this.sysRoleRepository.update({ role_id }, updateData);
const updatedRole = await this.getRoleById(role_id);
if (!updatedRole) {
throw new NotFoundException('角色更新后不存在');
@@ -51,15 +49,15 @@ export class CoreRoleService extends BaseService<SysRole> {
throw new NotFoundException('角色不存在');
}
await this.delete(role_id);
await this.sysRoleRepository.delete({ role_id });
}
async getRolesByAppType(site_id: number): Promise<SysRole[]> {
return await this.findMany({ site_id });
return await this.sysRoleRepository.find({ where: { site_id } });
}
async getActiveRolesByAppType(site_id: number): Promise<SysRole[]> {
return await this.findMany({ site_id, status: 1 });
return await this.sysRoleRepository.find({ where: { site_id, status: 1 } });
}
async isRoleNameExists(
@@ -78,9 +76,9 @@ export class CoreRoleService extends BaseService<SysRole> {
}
async getRoleStats(site_id: number): Promise<any> {
const total = await this.count({ site_id });
const active = await this.count({ site_id, status: 1 });
const inactive = await this.count({ site_id, status: 0 });
const total = await this.sysRoleRepository.count({ where: { site_id } });
const active = await this.sysRoleRepository.count({ where: { site_id, status: 1 } });
const inactive = await this.sysRoleRepository.count({ where: { site_id, status: 0 } });
return {
total,

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Post, Body, Query, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, Post, Body, Query, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -21,17 +21,21 @@ export class SiteAccountLogController {
@Get('page')
@ApiOperation({ summary: '账单分页' })
async page(@Query() query: SiteAccountLogQueryDto, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.service.getPage(siteId, query);
return { code: 200, message: '获取成功', data };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.getPage(siteId, query);
}
@Post()
@ApiOperation({ summary: '新增账单' })
async add(@Body() body: CreateSiteAccountLogDto, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.service.add(siteId, body);
return { code: 200, message: '创建成功', data };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.add(siteId, body);
}
}

View File

@@ -17,16 +17,14 @@ export class SiteController {
@Get('page')
@ApiOperation({ summary: '站点分页' })
async page(@Query() query: SiteQueryDto) {
const data = await this.service.getPage(query);
return { code: 200, message: '获取成功', data };
return await this.service.getPage(query);
}
@Get(':siteId')
@ApiOperation({ summary: '站点详情' })
@ApiParam({ name: 'siteId', description: '站点ID' })
async info(@Param('siteId', ParseIntPipe) siteId: number) {
const data = await this.service.getInfo(siteId);
return { code: 200, message: '获取成功', data };
return await this.service.getInfo(siteId);
}
@Post()
@@ -41,8 +39,7 @@ export class SiteController {
if (siteId && payload.siteId && payload.siteId !== siteId) {
throw new Error('越权操作site_id 不匹配');
}
const data = await this.service.add(payload, req);
return { code: 200, message: '创建成功', data };
return await this.service.add(payload, req);
}
@Put(':siteId')
@@ -58,8 +55,7 @@ export class SiteController {
if (requestSiteId && siteId !== requestSiteId) {
throw new Error('越权操作site_id 不匹配');
}
const data = await this.service.edit(siteId, payload, req);
return { code: 200, message: '更新成功', data };
return await this.service.edit(siteId, payload, req);
}
@Delete(':siteId')
@@ -71,8 +67,7 @@ export class SiteController {
if (requestSiteId && siteId !== requestSiteId) {
throw new Error('越权操作site_id 不匹配');
}
const data = await this.service.del(siteId, req);
return { code: 200, message: '删除成功', data };
return await this.service.del(siteId, req);
}
@Put(':siteId/status/:status')
@@ -85,7 +80,6 @@ export class SiteController {
if (requestSiteId && siteId !== requestSiteId) {
throw new Error('越权操作site_id 不匹配');
}
const data = await this.service.updateStatus(siteId, status, req);
return { code: 200, message: '更新成功', data };
return await this.service.updateStatus(siteId, status, req);
}
}

View File

@@ -17,38 +17,33 @@ export class SiteGroupController {
@Get('page')
@ApiOperation({ summary: '分组分页' })
async page(@Query() query: SiteGroupQueryDto) {
const data = await this.service.getPage(query);
return { code: 200, message: '获取成功', data };
return await this.service.getPage(query);
}
@Get(':group_id')
@ApiOperation({ summary: '分组详情' })
@ApiParam({ name: 'group_id', description: '分组ID' })
async info(@Param('group_id', ParseIntPipe) group_id: number) {
const data = await this.service.getInfo(group_id);
return { code: 200, message: '获取成功', data };
return await this.service.getInfo(group_id);
}
@Post()
@ApiOperation({ summary: '新增分组' })
async add(@Body() body: CreateSiteGroupDto) {
const data = await this.service.add(body);
return { code: 200, message: '创建成功', data };
return await this.service.add(body);
}
@Put(':group_id')
@ApiOperation({ summary: '编辑分组' })
@ApiParam({ name: 'group_id', description: '分组ID' })
async edit(@Param('group_id', ParseIntPipe) group_id: number, @Body() body: UpdateSiteGroupDto) {
const data = await this.service.edit(group_id, body);
return { code: 200, message: '更新成功', data };
return await this.service.edit(group_id, body);
}
@Delete(':group_id')
@ApiOperation({ summary: '删除分组' })
@ApiParam({ name: 'group_id', description: '分组ID' })
async del(@Param('group_id', ParseIntPipe) group_id: number) {
const data = await this.service.del(group_id);
return { code: 200, message: '删除成功', data };
return await this.service.del(group_id);
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Put, Post, Body, Param, Query, UseGuards, ParseIntPipe, Req } from '@nestjs/common';
import { Controller, Get, Put, Post, Body, Param, Query, UseGuards, ParseIntPipe, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -20,18 +20,22 @@ export class SiteUserController {
@Get('page')
@ApiOperation({ summary: '用户分页' })
async page(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.service.getPage(siteId, query);
return { code: 200, message: '获取成功', data };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.getPage(siteId, query);
}
@Get(':uid')
@ApiOperation({ summary: '用户详情' })
@ApiParam({ name: 'uid', description: '用户ID' })
async info(@Param('uid', ParseIntPipe) uid: number, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.service.getInfo(siteId, uid);
return { code: 200, message: '获取成功', data };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.getInfo(siteId, uid);
}
@Put(':uid/status/:status')
@@ -40,8 +44,7 @@ export class SiteUserController {
@Param('uid', ParseIntPipe) uid: number,
@Param('status', ParseIntPipe) status: number,
) {
const data = await this.service.updateStatus(uid, status);
return { code: 200, message: '更新成功', data };
return await this.service.updateStatus(uid, status);
}
@Post(':uid/roles')
@@ -51,9 +54,11 @@ export class SiteUserController {
@Body() body: { role_ids: string },
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const data = await this.service.assignRoles(siteId, uid, body.role_ids || '');
return { code: 200, message: '更新成功', data };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.assignRoles(siteId, uid, body.role_ids || '');
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Query, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, Query, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -20,9 +20,11 @@ export class UserLogController {
@Get('page')
@ApiOperation({ summary: '日志分页' })
async page(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.service.getPage(siteId, query);
return { code: 200, message: '获取成功', data };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.getPage(siteId, query);
}
}

View File

@@ -1,102 +1,104 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
import { BaseEntity } from "../../../core/base/BaseEntity";
/**
* 站点实体
* 对应PHP: app\model\site\Site
*/
@Entity("site")
export class Site extends BaseEntity {
export class Site {
@PrimaryGeneratedColumn({ name: "site_id" })
siteId: number;
site_id: number;
@Column({ name: "site_name", length: 100, comment: "站点名称" })
siteName: string;
@Column({ name: "site_name", type: "varchar", length: 50, default: "", comment: "站点名称" })
site_name: string;
@Column({ name: "front_end_name", length: 100, default: "", comment: "前端名称" })
frontEndName: string;
@Column({ name: "group_id", type: "int", default: 0, comment: "分组ID(0:不限制)" })
group_id: number;
@Column({ name: "front_end_logo", length: 500, default: "", comment: "前端Logo" })
frontEndLogo: string;
@Column({ name: "front_end_icon", length: 500, default: "", comment: "前端图标" })
frontEndIcon: string;
@Column({ name: "app_type", length: 20, default: "site", comment: "应用类型" })
appType: string;
@Column({ name: "keywords", length: 200, default: "", comment: "关键词" })
@Column({ name: "keywords", type: "varchar", length: 255, default: "", comment: "关键字" })
keywords: string;
@Column({ name: "logo", length: 500, default: "", comment: "Logo" })
@Column({ name: "app_type", type: "varchar", length: 50, default: "admin", comment: "站点类型" })
app_type: string;
@Column({ name: "logo", type: "varchar", length: 255, default: "", comment: "站点logo" })
logo: string;
@Column({ name: "icon", length: 500, default: "", comment: "图标" })
icon: string;
@Column({ name: "desc", type: "text", default: "", comment: "描述" })
@Column({ name: "desc", type: "varchar", length: 255, default: "", comment: "简介" })
desc: string;
@Column({ name: "status", type: "tinyint", default: 1, comment: "状态 0禁用 1启用" })
@Column({ name: "status", type: "tinyint", default: 1, comment: "状态 1-正常 0-体验期 2-已到期" })
status: number;
@Column({ name: "latitude", type: "decimal", precision: 10, scale: 6, default: 0, comment: "纬度" })
latitude: number;
@Column({ name: "latitude", type: "varchar", length: 255, default: "", comment: "纬度" })
latitude: string;
@Column({ name: "longitude", type: "decimal", precision: 10, scale: 6, default: 0, comment: "经度" })
longitude: number;
@Column({ name: "longitude", type: "varchar", length: 255, default: "", comment: "经度" })
longitude: string;
@Column({ name: "province_id", type: "int", default: 0, comment: "省份ID" })
provinceId: number;
@Column({ name: "province_id", type: "int", default: 0, comment: "省" })
province_id: number;
@Column({ name: "city_id", type: "int", default: 0, comment: "城市ID" })
cityId: number;
@Column({ name: "city_id", type: "int", default: 0, comment: "" })
city_id: number;
@Column({ name: "district_id", type: "int", default: 0, comment: "区县ID" })
districtId: number;
@Column({ name: "district_id", type: "int", default: 0, comment: "区" })
district_id: number;
@Column({ name: "address", length: 200, default: "", comment: "地址" })
@Column({ name: "address", type: "varchar", length: 255, default: "", comment: "详细地址" })
address: string;
@Column({ name: "full_address", length: 500, default: "", comment: "完整地址" })
fullAddress: string;
@Column({ name: "full_address", type: "varchar", length: 255, default: "", comment: "完整地址" })
full_address: string;
@Column({ name: "phone", length: 20, default: "", comment: "电话" })
@Column({ name: "phone", type: "varchar", length: 255, default: "", comment: "客服电话" })
phone: string;
@Column({ name: "business_hours", length: 200, default: "", comment: "营业时间" })
businessHours: string;
@Column({ name: "business_hours", type: "varchar", length: 255, default: "", comment: "营业时间" })
business_hours: string;
@Column({ name: "expire_time", type: "timestamp", nullable: true, comment: "过期时间" })
expireTime: Date;
@CreateDateColumn({ name: "create_time", type: "int", default: 0, comment: "创建时间" })
create_time: number;
@Column({ name: "group_id", type: "int", default: 0, comment: "分组ID" })
groupId: number;
@Column({ name: "expire_time", type: "bigint", default: 0, comment: "到期时间如果是0 无限期)" })
expire_time: number;
@Column({ name: "app", type: "json", nullable: true, comment: "应用配置" })
app: string[];
@Column({ name: "front_end_name", type: "varchar", length: 50, default: "", comment: "前台名称" })
front_end_name: string;
@Column({ name: "addons", type: "json", nullable: true, comment: "插件配置" })
addons: string[];
@Column({ name: "front_end_logo", type: "varchar", length: 255, default: "", comment: "前台logo" })
front_end_logo: string;
@Column({ name: "initalled_addon", type: "json", nullable: true, comment: "已安装插件" })
initalledAddon: string[];
@Column({ name: "front_end_icon", type: "varchar", length: 255, default: "", comment: "前台图标" })
front_end_icon: string;
@Column({ name: "site_domain", length: 200, default: "", comment: "站点域名" })
siteDomain: string;
@Column({ name: "icon", type: "varchar", length: 255, default: "", comment: "图标" })
icon: string;
@Column({ name: "meta_title", length: 200, default: "", comment: "SEO标题" })
metaTitle: string;
@Column({ name: "member_no", type: "varchar", length: 50, default: "0", comment: "会员编号" })
member_no: string;
@Column({ name: "meta_desc", length: 500, default: "", comment: "SEO描述" })
metaDesc: string;
@Column({ name: "app", type: "text", default: "", comment: "应用" })
app: string;
@Column({ name: "meta_keyword", length: 200, default: "", comment: "SEO关键词" })
metaKeyword: string;
@Column({ name: "addons", type: "text", default: "", comment: "插件" })
addons: string;
@CreateDateColumn({ name: "create_time", comment: "创建时间" })
createTime: Date;
@Column({ name: "initalled_addon", type: "text", default: "", comment: "已安装插件" })
initalled_addon: string;
@UpdateDateColumn({ name: "update_time", comment: "更新时间" })
updateTime: Date;
@Column({ name: "site_domain", type: "varchar", length: 255, default: "", comment: "站点域名" })
site_domain: string;
@Column({ name: "meta_title", type: "varchar", length: 255, default: "", comment: "Meta 标题" })
meta_title: string;
@Column({ name: "meta_desc", type: "varchar", length: 255, default: "", comment: "Meta 描述" })
meta_desc: string;
@UpdateDateColumn({ name: "update_time", type: "int", default: 0, comment: "更新时间" })
update_time: number;
@Column({ name: "is_del", type: "tinyint", default: 0, comment: "是否删除" })
is_del: number;
}

View File

@@ -1,4 +1,4 @@
import { Entity, Column } from 'typeorm';
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/**
@@ -7,6 +7,8 @@ import { BaseEntity } from '../../../core/base/BaseEntity';
*/
@Entity('site_account_log')
export class SiteAccountLog extends BaseEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ name: 'type', type: 'varchar', length: 50, comment: '类型pay支付refund退款transfer转账' })
type: string;

View File

@@ -1,5 +1,5 @@
import { Injectable } from "@nestjs/common";
import { CoreSiteGroupService } from "../core/CoreSiteGroupService";
import { SiteGroupCoreService } from "../core/SiteGroupCoreService";
import { SiteGroup } from "../../entities/SiteGroup";
/**
@@ -9,7 +9,7 @@ import { SiteGroup } from "../../entities/SiteGroup";
@Injectable()
export class SiteGroupService {
constructor(
private readonly coreSiteGroupService: CoreSiteGroupService,
private readonly coreSiteGroupService: SiteGroupCoreService,
) {}
/**

View File

@@ -110,28 +110,28 @@ export class SiteService {
this.normalizeExpire(payload);
// 校验 site_id 权限
if (payload.siteId) {
this.validateSiteId(payload.siteId, req);
if (payload.site_id) {
this.validateSiteId(payload.site_id, req);
}
const site = await this.coreSiteService.add(payload);
// 分组应用/插件同步到站点,并触发微页面加载
try {
if (payload.groupId) {
const group = await this.siteGroupCoreService.getInfo(Number(payload.groupId));
if (payload.group_id) {
const group = await this.siteGroupCoreService.getInfo(Number(payload.group_id));
if (group) {
await this.domainEventService.publishEvent(
'AddSiteAfter',
String(site.siteId ?? ''),
String(req?.user?.siteId ?? site.siteId ?? ''),
{ site_id: site.siteId, main_app: group.app || [], site_addons: group.addon || [] },
String(site.site_id ?? ''),
String(req?.user?.siteId ?? site.site_id ?? ''),
{ site_id: site.site_id, main_app: group.app || [], site_addons: group.addon || [] },
);
// 加载微页面数据(对齐 PHP: DiyService->loadDiyData
await this.domainEventService.publishEvent(
'site.diy.load',
String(site.siteId ?? ''),
String(req?.user?.siteId ?? site.siteId ?? ''),
{ site_id: site.siteId, main_app: group.app || [], tag: 'add' },
String(site.site_id ?? ''),
String(req?.user?.siteId ?? site.site_id ?? ''),
{ site_id: site.site_id, main_app: group.app || [], tag: 'add' },
);
}
}
@@ -141,14 +141,14 @@ export class SiteService {
try {
await this.domainEventService.publishEvent(
'site.site.added',
String(site.siteId ?? site['site_id'] ?? ''),
String((req?.user?.siteId ?? 0) || 0),
{ siteId: site.siteId ?? site['site_id'], payload },
String(site.site_id ?? ''),
String(req?.user?.siteId || site.site_id),
{ siteId: site.site_id, payload },
);
} catch {}
// 记录审计日志
await this.logAudit('site.create', site.siteId || 0, data, req);
return site.siteId;
await this.logAudit('site.create', site.site_id, data, req);
return site.site_id;
}
async edit(
@@ -167,12 +167,12 @@ export class SiteService {
await this.domainEventService.publishEvent(
'site.site.updated',
String(siteId),
String((req?.user?.siteId ?? 0) || 0),
String(req?.user?.siteId || siteId),
{ siteId, payload },
);
// 若分组变更,同步应用/插件并刷新微页面
if (payload.groupId) {
const group = await this.siteGroupCoreService.getInfo(Number(payload.groupId));
if (payload.group_id) {
const group = await this.siteGroupCoreService.getInfo(Number(payload.group_id));
if (group) {
await this.domainEventService.publishEvent(
'AddSiteAfter',
@@ -210,7 +210,7 @@ export class SiteService {
await this.domainEventService.publishEvent(
'site.site.deleted',
String(siteId),
String((req?.user?.siteId ?? 0) || 0),
String(req?.user?.siteId || siteId),
{ siteId },
);
}
@@ -232,7 +232,7 @@ export class SiteService {
await this.domainEventService.publishEvent(
'site.site.status_updated',
String(siteId),
String((req?.user?.siteId ?? 0) || 0),
String(req?.user?.siteId || siteId),
{ siteId, status },
);
}
@@ -267,7 +267,7 @@ export class SiteService {
}
// 触发初始化事件(对齐 PHP: event('SiteInit', ...)
try {
const group = site.groupId ? await this.siteGroupCoreService.getInfo(Number(site.groupId)) : null;
const group = site.group_id ? await this.siteGroupCoreService.getInfo(Number(site.group_id)) : null;
await this.domainEventService.publishEvent(
'SiteInit',
String(siteId),

View File

@@ -1,7 +1,6 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository, Like } from "typeorm";
import { BaseService } from "../../../../core/base/BaseService";
import { Site } from "../../entities/Site";
/**
@@ -9,13 +8,11 @@ import { Site } from "../../entities/Site";
* 对应PHP: app\service\core\site\CoreSiteService
*/
@Injectable()
export class CoreSiteService extends BaseService<Site> {
export class CoreSiteService {
constructor(
@InjectRepository(Site)
private readonly siteRepository: Repository<Site>,
) {
super(siteRepository);
}
) {}
/**
* 分页查询站点列表
@@ -27,12 +24,12 @@ export class CoreSiteService extends BaseService<Site> {
.createQueryBuilder("site")
.where("site.app_type != :appType", { appType: "admin" })
.select([
"site.siteId",
"site.siteName",
"site.frontEndName",
"site.frontEndLogo",
"site.frontEndIcon",
"site.appType",
"site.site_id",
"site.site_name",
"site.front_end_name",
"site.front_end_logo",
"site.front_end_icon",
"site.app_type",
"site.keywords",
"site.logo",
"site.icon",
@@ -40,23 +37,23 @@ export class CoreSiteService extends BaseService<Site> {
"site.status",
"site.latitude",
"site.longitude",
"site.provinceId",
"site.cityId",
"site.districtId",
"site.province_id",
"site.city_id",
"site.district_id",
"site.address",
"site.fullAddress",
"site.full_address",
"site.phone",
"site.businessHours",
"site.createTime",
"site.expireTime",
"site.groupId",
"site.business_hours",
"site.create_time",
"site.expire_time",
"site.group_id",
"site.app",
"site.addons",
"site.siteDomain",
"site.site_domain",
]);
if (where.siteName) {
queryBuilder.andWhere("site.siteName LIKE :siteName", {
queryBuilder.andWhere("site.site_name LIKE :siteName", {
siteName: `%${where.siteName}%`,
});
}
@@ -110,14 +107,14 @@ export class CoreSiteService extends BaseService<Site> {
*/
async getInfo(siteId: number) {
return await this.siteRepository.findOne({
where: { siteId },
where: { site_id: siteId },
select: [
"siteId",
"siteName",
"frontEndName",
"frontEndLogo",
"frontEndIcon",
"appType",
"site_id",
"site_name",
"front_end_name",
"front_end_logo",
"front_end_icon",
"app_type",
"keywords",
"logo",
"icon",
@@ -125,22 +122,21 @@ export class CoreSiteService extends BaseService<Site> {
"status",
"latitude",
"longitude",
"provinceId",
"cityId",
"districtId",
"province_id",
"city_id",
"district_id",
"address",
"fullAddress",
"full_address",
"phone",
"businessHours",
"createTime",
"expireTime",
"groupId",
"business_hours",
"create_time",
"expire_time",
"group_id",
"app",
"addons",
"siteDomain",
"metaTitle",
"metaDesc",
"metaKeyword",
"site_domain",
"meta_title",
"meta_desc",
],
});
}
@@ -153,7 +149,7 @@ export class CoreSiteService extends BaseService<Site> {
async add(data: Partial<Site>): Promise<Site> {
const siteData = {
...data,
appType: data.appType || "site",
appType: data.app_type || "site",
createTime: new Date(),
};
@@ -170,10 +166,10 @@ export class CoreSiteService extends BaseService<Site> {
async edit(siteId: number, data: Partial<Site>): Promise<boolean> {
const updateData = {
...data,
updateTime: new Date(),
update_time: Math.floor(Date.now() / 1000),
};
const result = await this.siteRepository.update(siteId, updateData);
const result = await this.siteRepository.update({ site_id: siteId }, updateData);
return (result.affected || 0) > 0;
}
@@ -220,10 +216,10 @@ export class CoreSiteService extends BaseService<Site> {
*/
async getExpireTime(siteId: number) {
const site = await this.siteRepository.findOne({
where: { siteId },
select: ["expireTime"],
where: { site_id: siteId },
select: ["expire_time"],
});
return site ? { expireTime: site.expireTime } : null;
return site ? { expireTime: site.expire_time } : null;
}
/**
@@ -233,7 +229,7 @@ export class CoreSiteService extends BaseService<Site> {
*/
async findByDomain(domain: string) {
return await this.siteRepository.findOne({
where: { siteDomain: domain },
where: { site_domain: domain },
});
}
@@ -244,9 +240,9 @@ export class CoreSiteService extends BaseService<Site> {
* @returns 是否成功
*/
async updateStatus(siteId: number, status: number): Promise<boolean> {
const result = await this.siteRepository.update(siteId, {
const result = await this.siteRepository.update({ site_id: siteId }, {
status,
updateTime: new Date(),
update_time: Math.floor(Date.now() / 1000),
});
return (result.affected || 0) > 0;
}
@@ -260,7 +256,7 @@ export class CoreSiteService extends BaseService<Site> {
const result = await this.siteRepository
.createQueryBuilder()
.update(Site)
.set({ status: 0, updateTime: new Date() })
.set({ status: 0, update_time: Math.floor(Date.now() / 1000) })
.where('status = :enabled', { enabled: 1 })
.andWhere('expire_time IS NOT NULL')
.andWhere('expire_time < :before', { before })

View File

@@ -28,6 +28,10 @@ export class SiteGroupCoreService {
return this.repo.findOne({ where: { group_id } });
}
async getAll() {
return this.repo.find();
}
async add(payload: Partial<SiteGroup>) {
const entity = this.repo.create({ ...payload, create_time: Math.floor(Date.now() / 1000) });
return this.repo.save(entity);
@@ -47,4 +51,9 @@ export class SiteGroupCoreService {
const list = await this.repo.find();
return list.map((g) => ({ id: (g as any).group_id, label: g.group_name, children: [] }));
}
async checkAddon(_addons: string[]) {
// 预留校验逻辑;与 PHP 行为对齐可在此实现
return true as any;
}
}

View File

@@ -1,4 +1,5 @@
import { Module } from "@nestjs/common";
import { CacheModule } from "@nestjs/cache-manager";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Site } from "./entities/Site";
import { SiteGroup } from "./entities/SiteGroup";
@@ -6,6 +7,8 @@ import { SiteAccountLog } from "./entities/SiteAccountLog";
import { SiteAccount } from "./entities/SiteAccount";
import { SysUserLog } from "./entities/SysUserLog";
import { DiyModule } from "../diy/diy.module";
import { SysUser } from "../admin/entities/SysUser";
import { SysUserRole } from "../admin/entities/SysUserRole";
// Core Services
import { CoreSiteService } from "./services/core/CoreSiteService";
@@ -33,8 +36,9 @@ import { DiyLoadEventHandler } from "./subscribers/diyLoadEventHandler";
*/
@Module({
imports: [
TypeOrmModule.forFeature([Site, SiteGroup, SiteAccountLog, SysUserLog, SiteAccount]),
TypeOrmModule.forFeature([Site, SiteGroup, SiteAccountLog, SysUserLog, SiteAccount, SysUser, SysUserRole]),
DiyModule,
CacheModule.register(),
],
providers: [
// Core Services

View File

@@ -17,7 +17,7 @@ export class DiyLoadEventHandler {
@EventHandler('site.diy.load', { consumerGroup: 'site-diy-initializer' })
async onLoad(event: DomainEvent): Promise<void> {
const data = (event?.data || {}) as { site_id?: number; main_app?: string[]; tag?: 'add' | 'update' };
const siteId = Number(data.site_id || 0);
const siteId = Number(data.site_id);
if (!siteId) return;
try {

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Param, Query, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, Param, Query, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import {
ApiTags,
ApiOperation,
@@ -42,9 +42,11 @@ export class AppController {
@Query('type') type?: string,
@Req() req?: AuthenticatedRequest,
) {
const siteId = req?.user?.siteId || 0;
const result = await this.appService.getAppList();
return { code: 200, message: '获取成功', data: result };
const siteId = req?.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.appService.getAppList();
}
@Get(':appKey')
@@ -55,9 +57,11 @@ export class AppController {
@Param('appKey') appKey: string,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.appService.getAppInfo(appKey);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.appService.getAppInfo(appKey);
}
@Get('check/:appKey')

View File

@@ -20,10 +20,10 @@ import { AreaService } from '../../services/admin/AreaService';
/**
* 地区管理控制器 - 管理端
* 路由前缀: /adminapi/sys/area
* 路由前缀: /admin/sys/area
*/
@ApiTags('地区管理')
@Controller('adminapi/sys/area')
@Controller('admin/sys/area')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AreaController {
@@ -34,8 +34,7 @@ export class AreaController {
@ApiQuery({ name: 'pid', description: '父级ID', required: false })
@ApiResponse({ status: 200, description: '获取成功' })
async getListByPid(@Query('pid', ParseIntPipe) pid: number = 0) {
const result = await this.areaService.getListByPid(pid);
return { code: 200, message: '获取成功', data: result };
return await this.areaService.getListByPid(pid);
}
@Get('tree')
@@ -43,8 +42,7 @@ export class AreaController {
@ApiQuery({ name: 'level', description: '最大层级', required: false })
@ApiResponse({ status: 200, description: '获取成功' })
async getAreaTree(@Query('level', ParseIntPipe) level: number = 3) {
const result = await this.areaService.getAreaTree(level);
return { code: 200, message: '获取成功', data: result };
return await this.areaService.getAreaTree(level);
}
@Get('search')
@@ -56,8 +54,7 @@ export class AreaController {
@Query('keyword') keyword: string,
@Query('level', ParseIntPipe) level?: number,
) {
const result = await this.areaService.searchArea(keyword, level);
return { code: 200, message: '获取成功', data: result };
return await this.areaService.searchArea(keyword, level);
}
@Get(':id')
@@ -65,8 +62,7 @@ export class AreaController {
@ApiParam({ name: 'id', description: '地区ID' })
@ApiResponse({ status: 200, description: '获取成功' })
async getAreaByAreaCode(@Param('id', ParseIntPipe) id: number) {
const result = await this.areaService.getAreaByAreaCode(id);
return { code: 200, message: '获取成功', data: result };
return await this.areaService.getAreaByAreaCode(id);
}
@Get(':id/path')
@@ -75,7 +71,7 @@ export class AreaController {
@ApiResponse({ status: 200, description: '获取成功' })
async getFullPath(@Param('id', ParseIntPipe) id: number) {
const result = await this.areaService.getFullPath(id);
return { code: 200, message: '获取成功', data: { path: result } };
return { path: result };
}
@Get('batch/:ids')
@@ -87,7 +83,6 @@ export class AreaController {
.split(',')
.map((id) => parseInt(id.trim()))
.filter((id) => !isNaN(id));
const result = await this.areaService.getAreaByAreaCodes(idArray);
return { code: 200, message: '获取成功', data: result };
return await this.areaService.getAreaByAreaCodes(idArray);
}
}

View File

@@ -10,6 +10,7 @@
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -48,9 +49,11 @@ export class AttachmentCategoryController {
@Query() query: AttachmentCategoryQueryDto,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentCategoryService.getPage(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentCategoryService.getPage(siteId, query);
}
@Get(':id')
@@ -61,9 +64,11 @@ export class AttachmentCategoryController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentCategoryService.getInfo(siteId, id);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentCategoryService.getInfo(siteId, id);
}
@Post()
@@ -73,13 +78,11 @@ export class AttachmentCategoryController {
@Body() data: CreateAttachmentCategoryDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentCategoryService.add(siteId, data);
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '创建失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentCategoryService.add(siteId, data);
}
@Put(':id')
@@ -91,17 +94,15 @@ export class AttachmentCategoryController {
@Body() data: UpdateAttachmentCategoryDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentCategoryService.edit(
siteId,
id,
data,
);
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '更新失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentCategoryService.edit(
siteId,
id,
data,
);
}
@Delete(':id')
@@ -112,12 +113,10 @@ export class AttachmentCategoryController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentCategoryService.del(siteId, id);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentCategoryService.del(siteId, id);
}
}

View File

@@ -10,6 +10,7 @@
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -52,9 +53,11 @@ export class AttachmentController {
@Query() query: AttachmentQueryDto,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentService.getPage(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentService.getPage(siteId, query);
}
@Get(':attId')
@@ -65,9 +68,11 @@ export class AttachmentController {
@Param('attId', ParseIntPipe) attId: number,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentService.getInfo(siteId, attId);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentService.getInfo(siteId, attId);
}
@Post()
@@ -77,13 +82,11 @@ export class AttachmentController {
@Body() data: CreateAttachmentDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentService.add(siteId, data);
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '创建失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentService.add(siteId, data);
}
@Put(':attId')
@@ -95,13 +98,11 @@ export class AttachmentController {
@Body() data: UpdateAttachmentDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentService.edit(siteId, attId, data);
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '更新失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentService.edit(siteId, attId, data);
}
@Put(':attId/category')
@@ -113,17 +114,15 @@ export class AttachmentController {
@Body() data: ModifyAttachmentCategoryDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentService.modifyCategory(
siteId,
attId,
data.cate_id,
);
return { code: 200, message: '修改成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '修改失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentService.modifyCategory(
siteId,
attId,
data.cate_id,
);
}
@Delete(':attId')
@@ -134,13 +133,11 @@ export class AttachmentController {
@Param('attId', ParseIntPipe) attId: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentService.del(siteId, attId);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentService.del(siteId, attId);
}
@Delete('batch')
@@ -150,15 +147,13 @@ export class AttachmentController {
@Body() data: BatchDeleteAttachmentDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.attachmentService.batchDelete(
siteId,
data.att_ids,
);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.attachmentService.batchDelete(
siteId,
data.att_ids,
);
}
}

View File

@@ -42,7 +42,7 @@ export class ChannelController {
@ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
// TODO: 实现渠道分页列表
return { code: 200, message: '获取成功', data: { list: [], total: 0 } };
return { list: [], total: 0 };
}
@Get('list')
@@ -50,7 +50,7 @@ export class ChannelController {
@ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
// TODO: 实现渠道列表
return { code: 200, message: '获取成功', data: [] };
return [];
}
@Get(':id')
@@ -62,7 +62,7 @@ export class ChannelController {
@Req() req: AuthenticatedRequest,
) {
// TODO: 实现渠道详情
return { code: 200, message: '获取成功', data: null };
return null;
}
@Post()
@@ -70,7 +70,7 @@ export class ChannelController {
@ApiResponse({ status: 200, description: '创建成功' })
async add(@Body() data: any, @Req() req: AuthenticatedRequest) {
// TODO: 实现渠道新增
return { code: 200, message: '创建成功', data: null };
return null;
}
@Put(':id')
@@ -83,7 +83,7 @@ export class ChannelController {
@Req() req: AuthenticatedRequest,
) {
// TODO: 实现渠道编辑
return { code: 200, message: '更新成功', data: null };
return null;
}
@Delete(':id')
@@ -95,6 +95,6 @@ export class ChannelController {
@Req() req: AuthenticatedRequest,
) {
// TODO: 实现渠道删除
return { code: 200, message: '删除成功', data: null };
return null;
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Post, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, Post, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -29,31 +29,47 @@ export class CommonController {
@ApiOperation({ summary: '获取字典数据' })
@ApiResponse({ status: 200, description: '获取成功' })
async getDict(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现字典数据获取
return { code: 200, message: '获取成功', data: {} };
return {};
}
@Get('config')
@ApiOperation({ summary: '获取系统配置' })
@ApiResponse({ status: 200, description: '获取成功' })
async getConfig(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现系统配置获取
return { code: 200, message: '获取成功', data: {} };
return {};
}
@Post('upload')
@ApiOperation({ summary: '文件上传' })
@ApiResponse({ status: 200, description: '上传成功' })
async upload(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现文件上传
return { code: 200, message: '上传成功', data: null };
return null;
}
@Get('captcha')
@ApiOperation({ summary: '获取验证码' })
@ApiResponse({ status: 200, description: '获取成功' })
async getCaptcha(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现验证码获取
return { code: 200, message: '获取成功', data: null };
return null;
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Post, Body, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, Post, Body, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -23,10 +23,10 @@ interface AuthenticatedRequest extends Request {
/**
* 系统配置控制器 - 管理端
* 路由前缀: /adminapi/sys/config
* 路由前缀: /admin/sys/config
*/
@ApiTags('系统配置管理')
@Controller('adminapi/sys/config')
@Controller('admin/sys/config')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class ConfigController {
@@ -36,13 +36,11 @@ export class ConfigController {
@ApiOperation({ summary: '获取版权信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getCopyright(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.configService.getCopyright(siteId);
return {
code: 200,
message: '获取成功',
data,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.getCopyright(siteId);
}
@Post('copyright')
@@ -52,26 +50,22 @@ export class ConfigController {
@Body() copyrightDto: CopyrightDto,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.configService.setCopyright(siteId, copyrightDto);
return {
code: 200,
message: '设置成功',
data: result,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.setCopyright(siteId, copyrightDto);
}
@Get('website')
@ApiOperation({ summary: '获取网站信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getWebSite(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.configService.getWebSite(siteId);
return {
code: 200,
message: '获取成功',
data,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.getWebSite(siteId);
}
@Post('website')
@@ -81,26 +75,22 @@ export class ConfigController {
@Body() websiteDto: WebSiteDto,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.configService.setWebSite(siteId, websiteDto);
return {
code: 200,
message: '设置成功',
data: result,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.setWebSite(siteId, websiteDto);
}
@Get('scene-domain')
@ApiOperation({ summary: '获取场景域名配置' })
@ApiResponse({ status: 200, description: '获取成功' })
async getSceneDomain(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.configService.getSceneDomain(siteId);
return {
code: 200,
message: '获取成功',
data,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.getSceneDomain(siteId);
}
@Post('scene-domain')
@@ -110,29 +100,25 @@ export class ConfigController {
@Body() sceneDomainDto: SceneDomainDto,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.configService.setSceneDomain(
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.setSceneDomain(
siteId,
sceneDomainDto,
);
return {
code: 200,
message: '设置成功',
data: result,
};
}
@Get('service')
@ApiOperation({ summary: '获取服务配置' })
@ApiResponse({ status: 200, description: '获取成功' })
async getService(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const data = await this.configService.getService(siteId);
return {
code: 200,
message: '获取成功',
data,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.getService(siteId);
}
@Post('service')
@@ -142,12 +128,10 @@ export class ConfigController {
@Body() serviceDto: ServiceDto,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.configService.setService(siteId, serviceDto);
return {
code: 200,
message: '设置成功',
data: result,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.setService(siteId, serviceDto);
}
}

View File

@@ -9,6 +9,7 @@
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -41,17 +42,18 @@ export class ExportController {
@ApiOperation({ summary: '获取导出记录分页列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.exportService.getPage(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.exportService.getPage(siteId, query);
}
@Get('data-types')
@ApiOperation({ summary: '获取导出数据类型列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getExportDataType(@Req() req: AuthenticatedRequest) {
const result = await this.exportService.getExportDataType();
return { code: 200, message: '获取成功', data: result };
return await this.exportService.getExportDataType();
}
@Post('check')
@@ -61,17 +63,15 @@ export class ExportController {
@Body() data: { export_key: string; conditions?: any },
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.exportService.checkExportData(
siteId,
data.export_key,
data.conditions,
);
return { code: 200, message: '检查成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '检查失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.exportService.checkExportData(
siteId,
data.export_key,
data.conditions,
);
}
@Post('export')
@@ -81,17 +81,15 @@ export class ExportController {
@Body() data: { export_key: string; conditions?: any },
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.exportService.exportData(
siteId,
data.export_key,
data.conditions,
);
return { code: 200, message: '导出任务创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '导出失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.exportService.exportData(
siteId,
data.export_key,
data.conditions,
);
}
@Delete(':id')
@@ -102,12 +100,10 @@ export class ExportController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.exportService.deleteRecord(siteId, id);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.exportService.deleteRecord(siteId, id);
}
}

View File

@@ -10,6 +10,7 @@
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -42,26 +43,29 @@ export class PosterController {
@ApiOperation({ summary: '获取海报分页列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.getPage(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.getPage(siteId, query);
}
@Get('list')
@ApiOperation({ summary: '获取海报列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.getList(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.getList(siteId, query);
}
@Get('types')
@ApiOperation({ summary: '获取海报类型列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getPosterTypes(@Req() req: AuthenticatedRequest) {
const result = await this.posterService.getPosterTypes();
return { code: 200, message: '获取成功', data: result };
return await this.posterService.getPosterTypes();
}
@Get('default/:type')
@@ -72,9 +76,11 @@ export class PosterController {
@Param('type') type: string,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.getDefaultByType(siteId, type);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.getDefaultByType(siteId, type);
}
@Get(':id')
@@ -85,22 +91,22 @@ export class PosterController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.getInfo(siteId, id);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.getInfo(siteId, id);
}
@Post()
@ApiOperation({ summary: '新增海报' })
@ApiResponse({ status: 200, description: '创建成功' })
async add(@Body() data: any, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.add(siteId, data);
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '创建失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.add(siteId, data);
}
@Put(':id')
@@ -112,13 +118,11 @@ export class PosterController {
@Body() data: any,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.edit(siteId, id, data);
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '更新失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.edit(siteId, id, data);
}
@Put(':id/default')
@@ -130,13 +134,11 @@ export class PosterController {
@Body() data: { type: string },
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.setDefault(siteId, id, data.type);
return { code: 200, message: '设置成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '设置失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.setDefault(siteId, id, data.type);
}
@Delete(':id')
@@ -147,12 +149,10 @@ export class PosterController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.posterService.del(siteId, id);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.posterService.del(siteId, id);
}
}

View File

@@ -10,6 +10,7 @@ import {
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -42,26 +43,29 @@ export class PrinterController {
@ApiOperation({ summary: '获取打印机分页列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.getPage(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.getPage(siteId, query);
}
@Get('list')
@ApiOperation({ summary: '获取打印机列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.getList(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.getList(siteId, query);
}
@Get('brands')
@ApiOperation({ summary: '获取打印机品牌列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getBrandList() {
const result = this.printerService.getBrandList();
return { code: 200, message: '获取成功', data: result };
return this.printerService.getBrandList();
}
@Get(':printerId')
@@ -72,22 +76,22 @@ export class PrinterController {
@Param('printerId', ParseIntPipe) printerId: number,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.getInfo(siteId, printerId);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.getInfo(siteId, printerId);
}
@Post()
@ApiOperation({ summary: '新增打印机' })
@ApiResponse({ status: 200, description: '创建成功' })
async add(@Body() data: any, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.add(siteId, data);
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '创建失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.add(siteId, data);
}
@Put(':printerId')
@@ -99,13 +103,11 @@ export class PrinterController {
@Body() data: any,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.edit(siteId, printerId, data);
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '更新失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.edit(siteId, printerId, data);
}
@Put(':printerId/status')
@@ -117,17 +119,15 @@ export class PrinterController {
@Body() data: { status: number },
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.modifyStatus(
siteId,
printerId,
data.status,
);
return { code: 200, message: '修改成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '修改失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.modifyStatus(
siteId,
printerId,
data.status,
);
}
@Post(':printerId/test')
@@ -138,16 +138,14 @@ export class PrinterController {
@Param('printerId', ParseIntPipe) printerId: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.testConnection(
siteId,
printerId,
);
return { code: 200, message: '测试完成', data: result };
} catch (error) {
return { code: 400, message: error.message || '测试失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.testConnection(
siteId,
printerId,
);
}
@Post(':printerId/print')
@@ -159,17 +157,15 @@ export class PrinterController {
@Body() data: { content: string },
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.print(
siteId,
printerId,
data.content,
);
return { code: 200, message: '打印完成', data: result };
} catch (error) {
return { code: 400, message: error.message || '打印失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.print(
siteId,
printerId,
data.content,
);
}
@Delete(':printerId')
@@ -180,12 +176,10 @@ export class PrinterController {
@Param('printerId', ParseIntPipe) printerId: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerService.del(siteId, printerId);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerService.del(siteId, printerId);
}
}

View File

@@ -10,6 +10,7 @@
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -44,26 +45,29 @@ export class PrinterTemplateController {
@ApiOperation({ summary: '获取打印模板分页列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.getPage(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.getPage(siteId, query);
}
@Get('list')
@ApiOperation({ summary: '获取打印模板列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.getList(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.getList(siteId, query);
}
@Get('types')
@ApiOperation({ summary: '获取模板类型列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getTemplateTypes(@Req() req: AuthenticatedRequest) {
const result = await this.printerTemplateService.getTemplateTypes();
return { code: 200, message: '获取成功', data: result };
return await this.printerTemplateService.getTemplateTypes();
}
@Get('by-type/:type')
@@ -74,12 +78,14 @@ export class PrinterTemplateController {
@Param('type') type: string,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.getTemplatesByType(
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.getTemplatesByType(
siteId,
type,
);
return { code: 200, message: '获取成功', data: result };
}
@Get(':templateId')
@@ -90,25 +96,25 @@ export class PrinterTemplateController {
@Param('templateId', ParseIntPipe) templateId: number,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.getInfo(
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.getInfo(
siteId,
templateId,
);
return { code: 200, message: '获取成功', data: result };
}
@Post()
@ApiOperation({ summary: '新增打印模板' })
@ApiResponse({ status: 200, description: '创建成功' })
async add(@Body() data: any, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.add(siteId, data);
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '创建失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.add(siteId, data);
}
@Put(':templateId')
@@ -120,17 +126,15 @@ export class PrinterTemplateController {
@Body() data: any,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.edit(
siteId,
templateId,
data,
);
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '更新失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.edit(
siteId,
templateId,
data,
);
}
@Post(':templateId/preview')
@@ -142,17 +146,15 @@ export class PrinterTemplateController {
@Body() data: { preview_data?: any },
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.previewTemplate(
siteId,
templateId,
data.preview_data,
);
return { code: 200, message: '预览成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '预览失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.previewTemplate(
siteId,
templateId,
data.preview_data,
);
}
@Delete(':templateId')
@@ -163,12 +165,10 @@ export class PrinterTemplateController {
@Param('templateId', ParseIntPipe) templateId: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.printerTemplateService.del(siteId, templateId);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.printerTemplateService.del(siteId, templateId);
}
}

View File

@@ -10,6 +10,7 @@ import {
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import {
ApiTags,
@@ -45,7 +46,7 @@ interface AuthenticatedRequest extends Request {
* 对应PHP: app\adminapi\controller\sys\Role
*/
@ApiTags('角色管理')
@Controller('adminapi/sys/role')
@Controller('admin/sys/role')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class RoleController {
@@ -58,46 +59,32 @@ export class RoleController {
@Query() query: RoleQueryDto,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.roleService.getPage(siteId, query);
return {
code: 200,
message: '获取成功',
data: result,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.roleService.getPage(siteId, query);
}
@Get('all')
@ApiOperation({ summary: '获取所有角色列表' })
@ApiOperation({ summary: '获取所有角色' })
@ApiResponse({ status: 200, description: '获取成功' })
async getAll(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const userId = req.user?.uid || 0;
// TODO: 实现用户权限检查
// 暂时假设为超级管理员,后续完善权限模块时补充
const isAdmin = true;
const userRoleIds: number[] = [];
const result = await this.roleService.getAll(siteId, userRoleIds, isAdmin);
return {
code: 200,
message: '获取成功',
data: result,
};
async getAll(@Query('siteId') siteId?: number, @Query('isAdmin') isAdmin?: boolean) {
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.roleService.getAll(siteId, [], isAdmin);
}
@Get('column')
@ApiOperation({ summary: '获取角色键值对' })
@ApiResponse({ status: 200, description: '获取成功' })
async getColumn(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.roleService.getColumn(siteId);
return {
code: 200,
message: '获取成功',
data: result,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.roleService.getColumn(siteId);
}
@Get(':roleId')
@@ -105,12 +92,7 @@ export class RoleController {
@ApiParam({ name: 'roleId', description: '角色ID' })
@ApiResponse({ status: 200, description: '获取成功' })
async getInfo(@Param('roleId', ParseIntPipe) roleId: number) {
const result = await this.roleService.getInfo(roleId);
return {
code: 200,
message: '获取成功',
data: result,
};
return await this.roleService.getInfo(roleId);
}
@Post()
@@ -120,31 +102,21 @@ export class RoleController {
@Body() createRoleDto: CreateRoleDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const appType = 'admin'; // 默认为admin类型
// 处理rules字段
const roleData = {
...createRoleDto,
rules: createRoleDto.rules
? JSON.stringify(createRoleDto.rules)
: undefined,
};
const result = await this.roleService.add(siteId, appType, roleData);
return {
code: 200,
message: '创建成功',
data: result,
};
} catch (error) {
return {
code: 400,
message: error.message || '创建失败',
data: null,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
const appType = 'admin'; // 默认为admin类型
// 处理rules字段
const roleData = {
...createRoleDto,
rules: createRoleDto.rules
? JSON.stringify(createRoleDto.rules)
: undefined,
};
return await this.roleService.add(siteId, appType, roleData);
}
@Put(':roleId')
@@ -156,30 +128,20 @@ export class RoleController {
@Body() updateRoleDto: UpdateRoleDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
// 处理rules字段
const roleData = {
...updateRoleDto,
rules: updateRoleDto.rules
? JSON.stringify(updateRoleDto.rules)
: undefined,
};
const result = await this.roleService.edit(roleId, siteId, roleData);
return {
code: 200,
message: '更新成功',
data: result,
};
} catch (error) {
return {
code: 400,
message: error.message || '更新失败',
data: null,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// 处理rules字段
const roleData = {
...updateRoleDto,
rules: updateRoleDto.rules
? JSON.stringify(updateRoleDto.rules)
: undefined,
};
return await this.roleService.edit(roleId, siteId, roleData);
}
@Put('status/:roleId')
@@ -191,25 +153,15 @@ export class RoleController {
@Body() modifyStatusDto: ModifyRoleStatusDto,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.roleService.modifyStatus(
roleId,
siteId,
modifyStatusDto.status,
);
return {
code: 200,
message: '修改成功',
data: result,
};
} catch (error) {
return {
code: 400,
message: error.message || '修改失败',
data: null,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.roleService.modifyStatus(
roleId,
siteId,
modifyStatusDto.status,
);
}
@Delete(':roleId')
@@ -220,21 +172,11 @@ export class RoleController {
@Param('roleId', ParseIntPipe) roleId: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.roleService.del(roleId, siteId);
return {
code: 200,
message: '删除成功',
data: result,
};
} catch (error) {
return {
code: 400,
message: error.message || '删除失败',
data: null,
};
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.roleService.del(roleId, siteId);
}
@Get('menu-ids/:roleIds')
@@ -251,7 +193,10 @@ export class RoleController {
@Query('allow_menu_keys') allowMenuKeys: string = '',
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
const roleIdArray = roleIds
.split(',')
.map((id) => parseInt(id.trim()))
@@ -263,15 +208,10 @@ export class RoleController {
.filter((key) => key)
: [];
const result = await this.roleService.getMenuIdsByRoleIds(
return await this.roleService.getMenuIdsByRoleIds(
siteId,
roleIdArray,
allowMenuKeyArray,
);
return {
code: 200,
message: '获取成功',
data: result,
};
}
}

View File

@@ -10,6 +10,7 @@
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -42,18 +43,22 @@ export class ScheduleController {
@ApiOperation({ summary: '获取定时任务分页列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.getPage(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.getPage(siteId, query);
}
@Get('list')
@ApiOperation({ summary: '获取定时任务列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.getList(siteId, query);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.getList(siteId, query);
}
@Get(':id')
@@ -64,22 +69,22 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.getInfo(siteId, id);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.getInfo(siteId, id);
}
@Post()
@ApiOperation({ summary: '新增定时任务' })
@ApiResponse({ status: 200, description: '创建成功' })
async add(@Body() data: any, @Req() req: AuthenticatedRequest) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.add(siteId, data);
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '创建失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.add(siteId, data);
}
@Put(':id')
@@ -91,13 +96,11 @@ export class ScheduleController {
@Body() data: any,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.edit(siteId, id, data);
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '更新失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.edit(siteId, id, data);
}
@Put(':id/start')
@@ -108,13 +111,11 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.start(siteId, id);
return { code: 200, message: '启动成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '启动失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.start(siteId, id);
}
@Put(':id/stop')
@@ -125,13 +126,11 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.stop(siteId, id);
return { code: 200, message: '停止成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '停止失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.stop(siteId, id);
}
@Delete(':id')
@@ -142,12 +141,10 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest,
) {
try {
const siteId = req.user?.siteId || 0;
const result = await this.scheduleService.del(siteId, id);
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.del(siteId, id);
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Query, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, Query, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -29,27 +29,35 @@ export class ScheduleLogController {
@ApiOperation({ summary: '获取定时任务日志分页列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现定时任务日志分页列表
return { code: 200, message: '获取成功', data: { list: [], total: 0 } };
return { list: [], total: 0 };
}
@Get('list')
@ApiOperation({ summary: '获取定时任务日志列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现定时任务日志列表
return { code: 200, message: '获取成功', data: [] };
return [];
}
@Get('stats')
@ApiOperation({ summary: '获取定时任务统计信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getStats(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现定时任务统计信息
return {
code: 200,
message: '获取成功',
data: { total: 0, success: 0, failed: 0 },
};
return { total: 0, success: 0, failed: 0 };
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Post, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, Post, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -17,10 +17,10 @@ interface AuthenticatedRequest extends Request {
/**
* 系统信息管理控制器 - 管理端
* 路由前缀: /adminapi/sys/system
* 路由前缀: /admin/sys/system
*/
@ApiTags('系统信息')
@Controller('adminapi/sys/system')
@Controller('admin/sys/system')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class SystemController {
@@ -30,52 +30,45 @@ export class SystemController {
@ApiOperation({ summary: '获取系统信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getInfo(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.getInfo();
return { code: 200, message: '获取成功', data: result };
return await this.systemService.getInfo();
}
@Get('url')
@ApiOperation({ summary: '获取系统URL信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getUrl(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.systemService.getUrl(siteId);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.systemService.getUrl(siteId);
}
@Get('stats')
@ApiOperation({ summary: '获取系统统计信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getSystemStats(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.getSystemStats();
return { code: 200, message: '获取成功', data: result };
return await this.systemService.getSystemStats();
}
@Get('system-info')
@ApiOperation({ summary: '获取详细系统信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getSystemInfo(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.getSystemInfo();
return { code: 200, message: '获取成功', data: result };
return await this.systemService.getSystemInfo();
}
@Post('clear-cache')
@ApiOperation({ summary: '清理系统缓存' })
@ApiResponse({ status: 200, description: '清理成功' })
async clearCache(@Req() req: AuthenticatedRequest) {
try {
const result = await this.systemService.clearCache();
return { code: 200, message: '清理成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '清理失败', data: null };
}
return await this.systemService.clearCache();
}
@Get('check-config')
@ApiOperation({ summary: '检查系统配置' })
@ApiResponse({ status: 200, description: '检查完成' })
async checkSystemConfig(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.checkSystemConfig();
return { code: 200, message: '检查完成', data: result };
return await this.systemService.checkSystemConfig();
}
}

View File

@@ -16,10 +16,10 @@ interface AuthenticatedRequest extends Request {
/**
* 富文本编辑器控制器 - 管理端
* 路由前缀: /adminapi/sys/ueditor
* 路由前缀: /admin/sys/ueditor
*/
@ApiTags('富文本编辑器')
@Controller('adminapi/sys/ueditor')
@Controller('admin/sys/ueditor')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class UeditorController {
@@ -49,7 +49,7 @@ export class UeditorController {
@ApiResponse({ status: 200, description: '上传成功' })
async upload(@Req() req: AuthenticatedRequest) {
// TODO: 实现编辑器文件上传
return { code: 200, message: '上传成功', data: null };
return null;
}
@Get('list')
@@ -57,6 +57,6 @@ export class UeditorController {
@ApiResponse({ status: 200, description: '获取成功' })
async getFileList(@Query() query: any, @Req() req: AuthenticatedRequest) {
// TODO: 实现文件列表获取
return { code: 200, message: '获取成功', data: { list: [], total: 0 } };
return { list: [], total: 0 };
}
}

View File

@@ -6,6 +6,7 @@
Req,
ParseIntPipe,
Query,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -34,9 +35,11 @@ export class ApiAreaController {
@Req() req: AuthenticatedRequest,
@Query('parent_id') parentId?: number,
) {
const siteId = req.user?.siteId || 0;
const result = await this.apiAreaService.getAreaList(siteId, parentId);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiAreaService.getAreaList(siteId, parentId);
}
@Get(':areaId')
@@ -47,8 +50,10 @@ export class ApiAreaController {
@Param('areaId', ParseIntPipe) areaId: number,
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.apiAreaService.getAreaInfo(siteId, areaId);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiAreaService.getAreaInfo(siteId, areaId);
}
}

View File

@@ -6,6 +6,7 @@
Query,
UseGuards,
Req,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -22,7 +23,7 @@ interface AuthenticatedRequest extends Request {
}
@ApiTags('API配置')
@Controller('api/sys/config')
@Controller('api/sys/settings')
@UseGuards(JwtAuthGuard)
export class ApiConfigController {
constructor(private readonly apiConfigService: ApiConfigService) {}
@@ -31,9 +32,11 @@ export class ApiConfigController {
@ApiOperation({ summary: '获取配置' })
@ApiResponse({ status: 200, description: '获取成功' })
async getConfig(@Query('key') key: string, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.apiConfigService.getConfig(siteId, key);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiConfigService.getConfig(siteId, key);
}
@Post('batch')
@@ -43,8 +46,10 @@ export class ApiConfigController {
@Body() body: { keys: string[] },
@Req() req: AuthenticatedRequest,
) {
const siteId = req.user?.siteId || 0;
const result = await this.apiConfigService.getConfigs(siteId, body.keys);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiConfigService.getConfigs(siteId, body.keys);
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { Controller, Get, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -14,7 +14,7 @@ interface AuthenticatedRequest extends Request {
}
@ApiTags('API首页')
@Controller('api/sys/index')
@Controller('api/sys/home')
@UseGuards(JwtAuthGuard)
export class ApiIndexController {
constructor(private readonly apiIndexService: ApiIndexService) {}
@@ -23,16 +23,17 @@ export class ApiIndexController {
@ApiOperation({ summary: '获取首页信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getIndexInfo(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.apiIndexService.getIndexInfo(siteId);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiIndexService.getIndexInfo(siteId);
}
@Get('system')
@ApiOperation({ summary: '获取系统信息' })
@ApiResponse({ status: 200, description: '获取成功' })
async getSystemInfo(@Req() req: AuthenticatedRequest) {
const result = await this.apiIndexService.getSystemInfo();
return { code: 200, message: '获取成功', data: result };
return await this.apiIndexService.getSystemInfo();
}
}

View File

@@ -1,4 +1,4 @@
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { Controller, Post, Body, UseGuards, Req, UnauthorizedException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -26,8 +26,11 @@ export class ApiScanController {
@Body() body: { code: string },
@Req() req: AuthenticatedRequest,
) {
const result = await this.apiScanService.scanQrCode(body.code);
return { code: 200, message: '扫描成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiScanService.scanQrCode(body.code);
}
@Post('barcode')
@@ -37,7 +40,10 @@ export class ApiScanController {
@Body() body: { code: string },
@Req() req: AuthenticatedRequest,
) {
const result = await this.apiScanService.scanBarcode(body.code);
return { code: 200, message: '扫描成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiScanService.scanBarcode(body.code);
}
}

View File

@@ -8,6 +8,7 @@
UseGuards,
Req,
ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express';
@@ -33,18 +34,22 @@ export class ApiTaskController {
@ApiOperation({ summary: '获取任务列表' })
@ApiResponse({ status: 200, description: '获取成功' })
async getTaskList(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.apiTaskService.getTaskList(siteId);
return { code: 200, message: '获取成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiTaskService.getTaskList(siteId);
}
@Post()
@ApiOperation({ summary: '创建任务' })
@ApiResponse({ status: 200, description: '创建成功' })
async createTask(@Body() body: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0;
const result = await this.apiTaskService.createTask(siteId, body);
return { code: 200, message: '创建成功', data: result };
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiTaskService.createTask(siteId, body);
}
@Put(':taskId/status')
@@ -56,10 +61,13 @@ export class ApiTaskController {
@Body() body: { status: string },
@Req() req: AuthenticatedRequest,
) {
const result = await this.apiTaskService.updateTaskStatus(
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.apiTaskService.updateTaskStatus(
taskId,
body.status,
);
return { code: 200, message: '更新成功', data: result };
}
}

View File

@@ -1,8 +1,7 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('sys_attachment')
export class SysAttachment extends BaseEntity {
export class SysAttachment {
@PrimaryGeneratedColumn({ name: 'att_id' })
att_id: number;
@@ -81,6 +80,15 @@ export class SysAttachment extends BaseEntity {
})
storage_type: string;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '上传时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
update_time: number;
// 获取文件扩展名
getFileExtension(): string {
return this.real_name.split('.').pop() || '';

View File

@@ -1,44 +1,49 @@
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
/**
* 系统配置实体
* 对应数据表: sys_config
*/
@Entity('sys_config')
@Index(['siteId', 'key'], { unique: true })
export class SysConfig extends BaseEntity {
@Index(['site_id', 'config_key'], { unique: true })
export class SysConfig {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点ID' })
siteId: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ name: 'key', type: 'varchar', length: 100, comment: '配置' })
key: string;
@Column({ name: 'config_key', type: 'varchar', length: 255, default: '', comment: '配置项关键字' })
config_key: string;
@Column({
name: 'value',
type: 'text',
nullable: true,
comment: '配置值(JSON格式)',
comment: '配置值json',
})
value: string;
@Column({
name: 'desc',
type: 'varchar',
length: 255,
nullable: true,
comment: '配置描述',
})
desc?: string;
@Column({
name: 'is_use',
name: 'status',
type: 'tinyint',
default: 1,
comment: '是否启用:0=否,1=是',
comment: '是否启用 1启用 0不启用',
})
isUse: number;
status: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '创建时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '修改时间' })
update_time: number;
@Column({ name: 'addon', type: 'varchar', length: 255, default: '', comment: '所属插件' })
addon: string;
@Column({ name: 'is_del', type: 'tinyint', default: 0, comment: '是否删除' })
is_del: number;
@DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
delete_time: number;
}

View File

@@ -6,26 +6,26 @@
UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/**
* 系统菜单实体
*/
@Entity('sys_menu')
export class SysMenu extends BaseEntity {
export class SysMenu {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'menu_name', length: 100, comment: '菜单名称' })
menuName: string;
@Column({ name: 'menu_name', type: 'varchar', length: 100, default: '', comment: '菜单名称' })
menu_name: string;
@Column({
name: 'menu_short_name',
type: 'varchar',
length: 50,
nullable: true,
comment: '菜单简称',
})
menuShortName: string;
menu_short_name: string;
@Column({
name: 'menu_type',
@@ -33,34 +33,37 @@ export class SysMenu extends BaseEntity {
default: 1,
comment: '菜单类型 1目录 2菜单 3按钮',
})
menuType: number;
menu_type: number;
@Column({ name: 'parent_id', type: 'int', default: 0, comment: '父级菜单ID' })
parentId: number;
parent_id: number;
@Column({
name: 'menu_key',
type: 'varchar',
length: 100,
nullable: true,
comment: '菜单标识',
})
menuKey: string;
menu_key: string;
@Column({
name: 'menu_url',
type: 'varchar',
length: 255,
nullable: true,
comment: '菜单链接',
})
menuUrl: string;
menu_url: string;
@Column({
name: 'menu_icon',
type: 'varchar',
length: 100,
nullable: true,
comment: '菜单图标',
})
menuIcon: string;
menu_icon: string;
@Column({ name: 'sort', type: 'int', default: 0, comment: '排序' })
sort: number;
@@ -79,7 +82,7 @@ export class SysMenu extends BaseEntity {
default: 1,
comment: '是否显示 0不显示 1显示',
})
isShow: number;
is_show: number;
@Column({
name: 'is_del',
@@ -87,16 +90,19 @@ export class SysMenu extends BaseEntity {
default: 0,
comment: '是否删除 0未删除 1已删除',
})
isDel: number;
is_del: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' })
createTime: Date;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '创建时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' })
updateTime: Date;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
update_time: number;
@DeleteDateColumn({ name: 'delete_time', comment: '删除时间' })
deleteTime: Date;
@DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
delete_time: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
// 虚拟字段
statusName?: string;

View File

@@ -5,41 +5,48 @@
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/**
* 系统通知实体
*/
@Entity('sys_notice')
export class SysNotice extends BaseEntity {
export class SysNotice {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'title', length: 200, comment: '通知标题' })
title: string;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点ID' })
site_id: number;
@Column({ name: 'content', type: 'text', comment: '通知内容' })
content: string;
@Column({ name: 'key', type: 'varchar', length: 50, default: '', comment: '标识' })
key: string;
@Column({
name: 'type',
type: 'tinyint',
default: 1,
comment: '通知类型 1系统通知 2用户通知',
})
type: number;
@Column({ name: 'sms_content', type: 'text', nullable: true, comment: '短信配置参数' })
sms_content: string;
@Column({
name: 'status',
type: 'tinyint',
default: 1,
comment: '状态 0禁用 1启用',
})
status: number;
@Column({ name: 'is_wechat', type: 'tinyint', default: 0, comment: '公众号模板消息0关闭1开启' })
is_wechat: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' })
createTime: Date;
@Column({ name: 'is_weapp', type: 'tinyint', default: 0, comment: '小程序订阅消息0关闭1开启' })
is_weapp: number;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' })
updateTime: Date;
@Column({ name: 'is_sms', type: 'tinyint', default: 0, comment: '发送短信0关闭1开启' })
is_sms: number;
@Column({ name: 'wechat_template_id', type: 'varchar', length: 255, default: '', comment: '微信模版消息id' })
wechat_template_id: string;
@Column({ name: 'weapp_template_id', type: 'varchar', length: 255, default: '', comment: '微信小程序订阅消息id' })
weapp_template_id: string;
@Column({ name: 'sms_id', type: 'varchar', length: 255, default: '', comment: '短信id对应短信配置' })
sms_id: string;
@Column({ name: 'wechat_first', type: 'varchar', length: 255, default: '', comment: '微信头部' })
wechat_first: string;
@Column({ name: 'wechat_remark', type: 'varchar', length: 255, default: '', comment: '微信说明' })
wechat_remark: string;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '添加时间' })
create_time: number;
}

View File

@@ -4,45 +4,40 @@
Column,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/**
* 系统角色实体
*/
@Entity('sys_role')
export class SysRole extends BaseEntity {
export class SysRole {
@PrimaryGeneratedColumn({ name: 'role_id' })
roleId: number;
role_id: number;
@Column({ name: 'role_name', length: 50, comment: '角色名称' })
roleName: string;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ name: 'role_key', length: 50, comment: '角色标识' })
roleKey: string;
@Column({ name: 'role_name', type: 'varchar', length: 255, default: '', comment: '角色名称' })
role_name: string;
@Column({ name: 'sort', type: 'int', default: 0, comment: '排序' })
sort: number;
@Column({ name: 'rules', type: 'text', nullable: true, comment: '角色权限(menus_id)' })
rules: string;
@Column({
name: 'status',
type: 'tinyint',
default: 1,
comment: '状态 0禁用 1启用',
})
@Column({ name: 'status', type: 'tinyint', default: 1, comment: '状态' })
status: number;
@Column({ name: 'remark', length: 500, nullable: true, comment: '备注' })
remark: string;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '添加时间' })
create_time: number;
@Column({ name: 'rules', type: 'json', nullable: true, comment: '权限规则' })
rules: string[];
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '最后修改时间' })
update_time: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' })
createTime: Date;
@Column({ name: 'is_del', type: 'tinyint', default: 0, comment: '是否删除' })
is_del: number;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' })
updateTime: Date;
@DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
delete_time: number;
// 虚拟字段
statusName?: string;

View File

@@ -4,63 +4,52 @@
Column,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/**
* 定时任务实体
*/
@Entity('sys_schedule')
export class SysSchedule extends BaseEntity {
export class SysSchedule {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'key', length: 100, comment: '任务标识' })
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ name: 'addon', type: 'varchar', length: 255, default: '', comment: '所属插件' })
addon: string;
@Column({ name: 'key', type: 'varchar', length: 255, default: '', comment: '计划任务模板key' })
key: string;
@Column({ name: 'title', length: 100, comment: '任务标题' })
title: string;
@Column({ name: 'command', length: 500, comment: '执行命令' })
command: string;
@Column({
name: 'time',
type: 'json',
nullable: true,
comment: '执行时间配置',
})
time: any;
@Column({
name: 'last_time',
type: 'timestamp',
nullable: true,
comment: '最后执行时间',
})
lastTime: Date;
@Column({
name: 'next_time',
type: 'timestamp',
nullable: true,
comment: '下次执行时间',
})
nextTime: Date;
@Column({
name: 'status',
type: 'tinyint',
default: 1,
comment: '状态 0禁用 1启用',
})
@Column({ name: 'status', type: 'int', default: 1, comment: '任务状态 是否启用' })
status: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' })
createTime: Date;
@Column({ name: 'time', type: 'varchar', length: 500, default: '', comment: '任务周期 json结构' })
time: string;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' })
updateTime: Date;
@Column({ name: 'count', type: 'int', default: 0, comment: '执行次数' })
count: number;
@Column({ name: 'last_time', type: 'int', default: 0, comment: '最后执行时间' })
last_time: number;
@Column({ name: 'next_time', type: 'int', default: 0, comment: '下次执行时间' })
next_time: number;
@Column({ name: 'sort', type: 'int', default: 0, comment: '排序' })
sort: number;
@CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '创建时间' })
create_time: number;
@UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
update_time: number;
@DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
delete_time: number;
// 虚拟字段
statusName?: string;

View File

@@ -24,7 +24,8 @@ export class RoleService {
* 删除用户组
*/
async delete(role_id: number) {
return this.coreRoleService.delete(role_id);
// Core 层方法名为 del且需要 siteId暂无 siteId 时传 0 与 PHP 语义对齐
return this.coreRoleService.del(role_id as any, 0 as any);
}
/**

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import { BaseService } from '../../../../core/base/BaseService';
import { SysAttachment } from '../../entities/SysAttachment';
/**
@@ -9,13 +8,11 @@ import { SysAttachment } from '../../entities/SysAttachment';
* 对应PHP: CoreAttachmentService
*/
@Injectable()
export class CoreAttachmentService extends BaseService<SysAttachment> {
export class CoreAttachmentService {
constructor(
@InjectRepository(SysAttachment)
private readonly attachmentRepository: Repository<SysAttachment>,
) {
super(attachmentRepository);
}
) {}
/**
* 分页查询附件列表
@@ -42,8 +39,8 @@ export class CoreAttachmentService extends BaseService<SysAttachment> {
'attachment.real_name',
'attachment.path',
'attachment.url',
'attachment.file_size',
'attachment.file_type',
'attachment.att_size',
'attachment.att_type',
'attachment.cate_id',
'attachment.create_time',
]);

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import { BaseService } from '../../../../core/base/BaseService';
import { SysRole } from '../../../rbac/entities/SysRole';
/**
@@ -9,13 +8,11 @@ import { SysRole } from '../../../rbac/entities/SysRole';
* 对应PHP: 角色核心操作逻辑
*/
@Injectable()
export class CoreRoleService extends BaseService<SysRole> {
export class CoreRoleService {
constructor(
@InjectRepository(SysRole)
private readonly roleRepository: Repository<SysRole>,
) {
super(roleRepository);
}
) {}
/**
* 分页查询角色列表

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