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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,14 @@ import { AddonService } from '../../services/admin/AddonService';
export class UpgradeController { export class UpgradeController {
constructor(private readonly addonService: AddonService) {} 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( async upgrade(
@Param('addon') addon: string, @Param('addon') addon: string,
@Body() dto: { is_need_backup?: boolean; is_need_cloudbuild?: boolean }, @Body() dto: { is_need_backup?: boolean; is_need_cloudbuild?: boolean },
@@ -31,7 +38,12 @@ export class UpgradeController {
return this.addonService.executeUpgrade(); 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) { async getUpgradeContent(@Param('addon') addon: string) {
return this.addonService.getUpgradeContent(addon); return this.addonService.getUpgradeContent(addon);
} }
@@ -41,7 +53,12 @@ export class UpgradeController {
return this.addonService.getUpgradeTask(); 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) { async upgradePreCheck(@Param('addon') addon: string) {
return this.addonService.upgradePreCheck(addon); return this.addonService.upgradePreCheck(addon);
} }

View File

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

View File

@@ -1,10 +1,9 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { SysUserRole } from './SysUserRole'; import { SysUserRole } from './SysUserRole';
import { SysUserLog } from './SysUserLog'; import { SysUserLog } from './SysUserLog';
@Entity('sys_user') @Entity('sys_user')
export class SysUser extends BaseEntity { export class SysUser {
@PrimaryGeneratedColumn({ name: 'uid' }) @PrimaryGeneratedColumn({ name: 'uid' })
uid: number; uid: number;
@@ -32,6 +31,18 @@ export class SysUser extends BaseEntity {
@Column({ name: 'status', type: 'tinyint', default: 1 }) @Column({ name: 'status', type: 'tinyint', default: 1 })
status: number; 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) @OneToMany(() => SysUserRole, (userRole) => userRole.user)
user_role: SysUserRole[]; user_role: SysUserRole[];

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -48,4 +48,26 @@ export class LoginConfigDto {
lockoutDuration?: number; lockoutDuration?: number;
lockoutType?: string; 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 { Injectable } from '@nestjs/common';
import { CoreLoginConfigService } from '../core/CoreLoginConfigService'; import { CoreLoginConfigService, LoginConfig } from '../core/CoreLoginConfigService';
import { LoginConfigDto } from '../../dto/admin/LoginConfigDto'; import { LoginConfigDto } from '../../dto/admin/LoginConfigDto';
@Injectable() @Injectable()
export class LoginConfigService { export class LoginConfigService {
constructor(private readonly coreLoginConfig: CoreLoginConfigService) {} constructor(private readonly coreLoginConfig: CoreLoginConfigService) {}
async getConfig() { async getConfig(siteId: number): Promise<LoginConfig> {
return await this.coreLoginConfig.getConfig(); return await this.coreLoginConfig.getConfig(siteId);
} }
async setConfig(dto: LoginConfigDto) { async setConfig(dto: LoginConfigDto, siteId: number) {
return await this.coreLoginConfig.setConfig(dto); 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'; import { CoreLoginConfigService } from '../core/CoreLoginConfigService';
@Injectable() @Injectable()
@@ -9,35 +9,55 @@ export class LoginConfigApiService {
* 获取登录配置 * 获取登录配置
*/ */
async getInfo(query: any) { 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) { 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) { 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) { 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) { 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 { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { SysUser } from '../../../admin/entities/SysUser'; import { SysUser } from '../../../admin/entities/SysUser';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
@Injectable() @Injectable()
export class CoreAuthService extends BaseService<SysUser> { export class CoreAuthService {
constructor( constructor(
@InjectRepository(SysUser) @InjectRepository(SysUser)
private userRepository: Repository<SysUser>, private readonly userRepository: Repository<SysUser>,
) { ) {}
super(userRepository);
}
/** /**
* 验证用户凭据 * 验证用户凭据
*/ */
async validateUser(username: string, password: string, site_id: number) { async validateUser(username: string, password: string, site_id: number) {
const user = await this.userRepository.findOne({ const user = await this.userRepository.findOne({
where: { username, site_id, status: 1 }, where: { username, status: 1 },
}); });
if (!user) { if (!user) {
@@ -49,7 +46,7 @@ export class CoreAuthService extends BaseService<SysUser> {
*/ */
async checkUserExists(username: string, site_id: number) { async checkUserExists(username: string, site_id: number) {
const user = await this.userRepository.findOne({ const user = await this.userRepository.findOne({
where: { username, site_id }, where: { username },
}); });
return !!user; return !!user;
} }
@@ -60,15 +57,20 @@ export class CoreAuthService extends BaseService<SysUser> {
async createUser(userData: any) { async createUser(userData: any) {
const hashedPassword = await bcrypt.hash(userData.password, 10); const hashedPassword = await bcrypt.hash(userData.password, 10);
const user = this.userRepository.create({ const userDataWithHash = {
...userData, ...userData,
password: hashedPassword, password: hashedPassword,
status: 1, status: 1,
create_time: Math.floor(Date.now() / 1000), };
});
const saved = await this.userRepository.save(user); const user = this.userRepository.create({
return Array.isArray(saved) ? saved[0] : saved; ...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 { Injectable } from '@nestjs/common';
import { RedisProvider } from '../../../../vendor/redis/redis.provider';
import { CaptchaCreateDto, CaptchaCheckDto, CaptchaVerificationDto } from '../../dto/admin/CaptchaDto'; import { CaptchaCreateDto, CaptchaCheckDto, CaptchaVerificationDto } from '../../dto/admin/CaptchaDto';
@Injectable() @Injectable()
export class CoreCaptchaService { 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) { async create(dto: CaptchaCreateDto) {
// 对齐 PHP: CaptchaService->create() // 对齐 PHP: CaptchaService->create()
const captchaId = this.generateCaptchaId(); const captchaId = this.generateCaptchaId();
const captchaValue = this.generateCaptchaValue(dto.length || 4); const captchaValue = this.generateCaptchaValue(dto.length || 4);
// TODO: 生成验证码图片并存储 // 持久化到 Redis
// const captchaImage = await this.generateCaptchaImage(captchaValue, dto); const client = this.redisProvider.getClient();
await client.setex(
`${this.CAPTCHA_PREFIX}${captchaId}`,
this.CAPTCHA_TTL_SECONDS,
captchaValue,
);
return { return {
captchaId, captchaId,
@@ -23,7 +32,6 @@ export class CoreCaptchaService {
async check(dto: CaptchaCheckDto) { async check(dto: CaptchaCheckDto) {
// 对齐 PHP: CaptchaService->check() // 对齐 PHP: CaptchaService->check()
// TODO: 从缓存或数据库验证验证码
const isValid = await this.validateCaptcha(dto.captchaId, dto.captchaValue); const isValid = await this.validateCaptcha(dto.captchaId, dto.captchaValue);
if (!isValid) { if (!isValid) {
@@ -35,7 +43,6 @@ export class CoreCaptchaService {
async verification(dto: CaptchaVerificationDto) { async verification(dto: CaptchaVerificationDto) {
// 对齐 PHP: CaptchaService->verification() // 对齐 PHP: CaptchaService->verification()
// TODO: 二次验证逻辑,可能包括短信验证、邮箱验证等
const isValid = await this.validateCaptcha(dto.captchaId, dto.captchaValue); const isValid = await this.validateCaptcha(dto.captchaId, dto.captchaValue);
if (!isValid) { if (!isValid) {
@@ -71,14 +78,20 @@ export class CoreCaptchaService {
} }
private async validateCaptcha(captchaId: string, captchaValue: string): Promise<boolean> { private async validateCaptcha(captchaId: string, captchaValue: string): Promise<boolean> {
// TODO: 从Redis或数据库验证验证码 const client = this.redisProvider.getClient();
// 临时实现 const key = `${this.CAPTCHA_PREFIX}${captchaId}`;
return captchaValue.length >= 4; 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> { private async performSecondVerification(params?: Record<string, any>): Promise<boolean> {
// TODO: 实现二次验证逻辑 // 可以在此扩展短信/邮箱等二次校验
// 可能包括短信验证、邮箱验证、人脸识别等
return true; return true;
} }
} }

View File

@@ -1,12 +1,99 @@
import { Injectable } from '@nestjs/common'; 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'; import { LoginConfigDto } from '../../dto/admin/LoginConfigDto';
@Injectable() export interface LoginConfig {
export class CoreLoginConfigService { isCaptcha: number;
constructor() {} 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 { return {
isCaptcha: 1, // 默认启用验证码 isCaptcha: 1, // 默认启用验证码
isSiteCaptcha: 1, // 默认启用站点验证码 isSiteCaptcha: 1, // 默认启用站点验证码
@@ -14,8 +101,8 @@ export class CoreLoginConfigService {
siteBg: '', // 站点登录背景图 siteBg: '', // 站点登录背景图
loginMethods: { loginMethods: {
username: true, username: true,
email: true, email: false,
mobile: true, mobile: false,
wechat: false, wechat: false,
qq: false, qq: false,
}, },
@@ -30,68 +117,81 @@ export class CoreLoginConfigService {
lockoutDuration: 30, // 分钟 lockoutDuration: 30, // 分钟
lockoutType: 'ip', // ip 或 username lockoutType: 'ip', // ip 或 username
}, },
// PHP 特有字段
isAuthRegister: true,
isForceAccessUserInfo: false,
isBindMobile: false,
agreementShow: false,
desc: '精选好物,购物优惠的省钱平台',
}; };
} }
async setConfig(dto: LoginConfigDto) { async setConfig(dto: LoginConfigDto, siteId: number) {
// 对齐 PHP: ConfigService->setConfig() // 对齐 PHP: CoreMemberConfigService->setLoginConfig()
const config = { const config = {
isCaptcha: dto.isCaptcha ?? 1, is_username: dto.loginMethods?.username ? 1 : 0,
isSiteCaptcha: dto.isSiteCaptcha ?? 1, is_mobile: dto.loginMethods?.mobile ? 1 : 0,
bg: dto.bg ?? '', is_auth_register: dto.isAuthRegister ? 1 : 0,
siteBg: dto.siteBg ?? '', is_force_access_user_info: dto.isForceAccessUserInfo ? 1 : 0,
loginMethods: dto.loginMethods ?? { is_bind_mobile: dto.isBindMobile ? 1 : 0,
username: true, agreement_show: dto.agreementShow ? 1 : 0,
email: true, bg_url: dto.bg || '',
mobile: true, desc: dto.desc || '精选好物,购物优惠的省钱平台',
wechat: false,
qq: false,
},
passwordPolicy: dto.passwordPolicy ?? {
minLength: 6,
requireSpecialChar: false,
requireNumber: false,
requireUppercase: false,
},
loginLimit: dto.loginLimit ?? {
maxAttempts: 5,
lockoutDuration: 30,
lockoutType: 'ip',
},
}; };
// TODO: 保存配置到数据库或配置文件 const existed = await this.repository.findOne({
// await this.saveConfig(config); 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: '配置保存成功' }; return { success: true, message: '配置保存成功' };
} }
// 兼容 API 层调用的方法(按 PHP 语义拆分) // 兼容 API 层调用的方法(按 PHP 语义拆分)
async getInfo(query?: any) { async getInfo(siteId: number, _query?: any): Promise<LoginConfig> {
return this.getConfig(); return this.getConfig(siteId);
} }
async getMethods(query?: any) { async getMethods(siteId: number, _query?: any) {
const config = await this.getConfig(); const config = await this.getConfig(siteId);
return config.loginMethods; return config.loginMethods;
} }
async getCaptchaConfig(query?: any) { async getCaptchaConfig(siteId: number, _query?: any) {
const config = await this.getConfig(); const config = await this.getConfig(siteId);
return { isCaptcha: config.isCaptcha, isSiteCaptcha: config.isSiteCaptcha }; return { isCaptcha: config.isCaptcha, isSiteCaptcha: config.isSiteCaptcha };
} }
async getThirdPartyConfig(query?: any) { async getThirdPartyConfig(siteId: number, _query?: any) {
const config = await this.getConfig(); const config = await this.getConfig(siteId);
return { wechat: config.loginMethods.wechat, qq: config.loginMethods.qq }; return { wechat: config.loginMethods.wechat, qq: config.loginMethods.qq };
} }
async getRegisterConfig(query?: any) { async getRegisterConfig(siteId: number, _query?: any) {
const config = await this.getConfig(); const config = await this.getConfig(siteId);
return { passwordPolicy: config.passwordPolicy }; return { passwordPolicy: config.passwordPolicy };
} }
async getForgotPasswordConfig(query?: any) { getForgotPasswordConfig(_query?: any) {
return { ways: ['email', 'mobile'] }; 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 { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { DiyFormFields } from './DiyFormFields'; import { DiyFormFields } from './DiyFormFields';
import { DiyFormRecords } from './DiyFormRecords'; import { DiyFormRecords } from './DiyFormRecords';
@Entity('diy_form') @Entity('diy_form')
export class DiyForm extends BaseEntity { export class DiyForm {
@PrimaryGeneratedColumn({ name: 'form_id' }) @PrimaryGeneratedColumn({ name: 'form_id' })
form_id: number; 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; title: string;
@Column({ name: 'type', type: 'varchar', length: 50, comment: '表单类型' }) @Column({ name: 'type', type: 'varchar', length: 255, default: '', comment: '表单类型' })
type: string; type: string;
@Column({ name: 'value', type: 'text', nullable: true, comment: '表单配置JSON' }) @Column({ name: 'status', type: 'tinyint', default: 0, comment: '状态0关闭1开启' })
value: string;
@Column({ name: 'share', type: 'text', nullable: true, comment: '分享配置JSON' })
share: string;
@Column({ name: 'status', type: 'tinyint', default: 1, comment: '状态0=禁用,1=启用' })
status: number; status: number;
@Column({ name: 'is_default', type: 'tinyint', default: 0, comment: '是否默认0=否,1=是' }) @Column({ name: 'template', type: 'varchar', length: 255, default: '', comment: '模板名称' })
is_default: number; 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; addon: string;
@Column({ name: 'sort', type: 'int', default: 0, comment: '排序' }) @Column({ name: 'share', type: 'varchar', length: 1000, default: '', comment: '分享内容' })
sort: number; 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) @OneToMany(() => DiyFormFields, fields => fields.form)

View File

@@ -1,12 +1,11 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/** /**
* 自定义页面实体 * 自定义页面实体
* 对应数据库表: diy_page * 对应数据库表: diy_page
*/ */
@Entity('diy_page') @Entity('diy_page')
export class DiyPage extends BaseEntity { export class DiyPage {
@PrimaryGeneratedColumn({ name: 'id' }) @PrimaryGeneratedColumn({ name: 'id' })
id: number; id: number;
@@ -43,5 +42,12 @@ export class DiyPage extends BaseEntity {
@Column({ name: 'visit_count', type: 'int', default: 0, comment: '访问量' }) @Column({ name: 'visit_count', type: 'int', default: 0, comment: '访问量' })
visit_count: number; 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 { CoreDiyService } from '../core/CoreDiyService';
import { DiyPage } from '../../entities/DiyPage'; import { DiyPage } from '../../entities/DiyPage';
@@ -74,7 +74,10 @@ export class DiyService {
* 控制器所需:分页 * 控制器所需:分页
*/ */
async getPage(query: any) { 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 list = await this.coreDiyService.getPageListByType(Number(site_id), key);
const page = Number(query?.page || 1); const page = Number(query?.page || 1);
const limit = Number(query?.limit || list.length || 20); const limit = Number(query?.limit || list.length || 20);
@@ -88,9 +91,12 @@ export class DiyService {
} }
async add(data: any) { async add(data: any) {
if (!data.site_id) {
throw new UnauthorizedException('Missing site_id');
}
// 对齐 PHP 字段映射 // 对齐 PHP 字段映射
const payload = { const payload = {
site_id: data.site_id || 0, site_id: data.site_id,
name: data.page_name, name: data.page_name,
type: data.page_type || data.key || 'custom', type: data.page_type || data.key || 'custom',
value: JSON.stringify(data.page_data || {}), value: JSON.stringify(data.page_data || {}),
@@ -118,7 +124,7 @@ export class DiyService {
const info = await this.coreDiyService.getPageInfo(pageId); const info = await this.coreDiyService.getPageInfo(pageId);
if (!info) return null; if (!info) return null;
return this.coreDiyService.addPage({ return this.coreDiyService.addPage({
site_id: (info as any).site_id || 0, site_id: (info as any).site_id,
name: newName, name: newName,
type: (info as any).type, type: (info as any).type,
value: (info as any).value, value: (info as any).value,
@@ -136,7 +142,7 @@ export class DiyService {
const info = await this.coreDiyService.getPageInfo(pageId); const info = await this.coreDiyService.getPageInfo(pageId);
if (!info) return false; if (!info) return false;
// 对齐 PHP设置为默认 // 对齐 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; return true;
} }

View File

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

View File

@@ -1,13 +1,12 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { DiyPage } from '../../entities/DiyPage'; import { DiyPage } from '../../entities/DiyPage';
import { DiyRoute } from '../../entities/DiyRoute'; import { DiyRoute } from '../../entities/DiyRoute';
import { DiyTheme } from '../../entities/DiyTheme'; import { DiyTheme } from '../../entities/DiyTheme';
@Injectable() @Injectable()
export class CoreDiyService extends BaseService<DiyPage> { export class CoreDiyService {
constructor( constructor(
@InjectRepository(DiyPage) @InjectRepository(DiyPage)
private diyPageRepository: Repository<DiyPage>, private diyPageRepository: Repository<DiyPage>,
@@ -15,9 +14,7 @@ export class CoreDiyService extends BaseService<DiyPage> {
private diyRouteRepository: Repository<DiyRoute>, private diyRouteRepository: Repository<DiyRoute>,
@InjectRepository(DiyTheme) @InjectRepository(DiyTheme)
private diyThemeRepository: Repository<DiyTheme>, private diyThemeRepository: Repository<DiyTheme>,
) { ) {}
super(diyPageRepository);
}
/** /**
* 获取DIY页面列表 * 获取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) { async setDefaultPage(site_id: number, name: string, page_id: number) {
// 先取消其他页面的默认状态 // 先取消其他页面的默认状态
await this.diyPageRepository.update( 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 { 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 { JobsService } from './jobs.service';
import { OnModuleInit } from '@nestjs/common'; import { OnModuleInit } from '@nestjs/common';
import { PaymentProcessors } from './processors/payment'; import { PaymentProcessors } from './processors/payment';
@@ -12,7 +17,7 @@ import { UpgradeProcessors } from './processors/upgrade';
import { WxoplatformProcessors } from './processors/wxoplatform'; import { WxoplatformProcessors } from './processors/wxoplatform';
@Module({ @Module({
imports: [VendorModule], imports: [VendorModule, forwardRef(() => PayModule), SiteModule, SysModule, ScheduleModule, MemberModule],
providers: [ providers: [
JobsService, JobsService,
PaymentProcessors, PaymentProcessors,

View File

@@ -20,17 +20,17 @@ export class PaymentProcessors {
? await this.corePay.findByOutTradeNo(data.siteId, data.outTradeNo) ? await this.corePay.findByOutTradeNo(data.siteId, data.outTradeNo)
: await this.corePay.findByOutTradeNoUnsafe(data.outTradeNo); : await this.corePay.findByOutTradeNoUnsafe(data.outTradeNo);
if (!pay) return { skipped: true }; 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 adapter: PaymentAdapter = resolved.adapter as PaymentAdapter;
const config: unknown = resolved.config as unknown; const config: unknown = resolved.config as unknown;
const q: { status: string; tradeNo?: string } = await adapter.query( const q: { status: string; tradeNo?: string } = await adapter.query(
config as any, config as any,
pay.outTradeNo, pay.out_trade_no,
); );
if (q.status === 'SUCCESS') { 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') { } 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 }; 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, OneToMany,
ManyToOne, ManyToOne,
JoinColumn, JoinColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm'; } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
import { MemberAccount } from './MemberAccount'; import { MemberAccount } from './MemberAccount';
import { MemberCashOut } from './MemberCashOut'; import { MemberCashOut } from './MemberCashOut';
import { MemberLabel } from './MemberLabel'; import { MemberLabel } from './MemberLabel';
@@ -16,7 +17,7 @@ import { MemberAddress } from './MemberAddress';
import { MemberAccountLog } from './MemberAccountLog'; import { MemberAccountLog } from './MemberAccountLog';
@Entity('member') @Entity('member')
export class Member extends BaseEntity { export class Member {
@PrimaryGeneratedColumn({ name: 'member_id' }) @PrimaryGeneratedColumn({ name: 'member_id' })
member_id: number; member_id: number;
@@ -26,6 +27,9 @@ export class Member extends BaseEntity {
@Column({ name: 'pid', type: 'int', default: 0 }) @Column({ name: 'pid', type: 'int', default: 0 })
pid: number; pid: number;
@Column({ name: 'site_id', type: 'int', default: 0 })
site_id: number;
@Column({ name: 'username', type: 'varchar', length: 255, default: '' }) @Column({ name: 'username', type: 'varchar', length: 255, default: '' })
username: string; username: string;
@@ -214,6 +218,15 @@ export class Member extends BaseEntity {
@Column({ name: 'remark', type: 'varchar', length: 300, default: '' }) @Column({ name: 'remark', type: 'varchar', length: 300, default: '' })
remark: string; 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) @OneToMany(() => MemberAccount, (account) => account.member)
accounts: MemberAccount[]; accounts: MemberAccount[];

View File

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

View File

@@ -1,50 +1,37 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { BaseEntity } from '@wwjCore/base/BaseEntity';
import { Member } from './Member'; import { Member } from './Member';
@Entity('member_level') @Entity('member_level')
export class MemberLevel extends BaseEntity { export class MemberLevel {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
level_id: number; 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; level_name: string;
@Column({ type: 'varchar', length: 255, comment: '等级图标' }) @Column({ name: 'growth', type: 'int', default: 0, comment: '所需成长值' })
level_icon: string; growth: number;
@Column({ type: 'int', default: 0, comment: '升级所需积分' }) @Column({ name: 'remark', type: 'varchar', length: 255, default: '', comment: '备注' })
upgrade_point: number; remark: string;
@Column({ @Column({ name: 'status', type: 'int', default: 1, comment: '状态 0已禁用1已启用' })
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:禁用' })
status: number; status: number;
@Column({ type: 'varchar', length: 255, comment: '等级描述' }) @Column({ name: 'level_benefits', type: 'text', nullable: true, comment: '等级权益' })
description: string; level_benefits: string;
@Column({ type: 'varchar', length: 255, comment: '等级权益' }) @Column({ name: 'level_gifts', type: 'text', nullable: true, comment: '等级礼包' })
benefits: string; 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) @OneToMany(() => Member, (member) => member.level)

View File

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

View File

@@ -1,17 +1,14 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { MemberAddress } from '../../entities/MemberAddress'; import { MemberAddress } from '../../entities/MemberAddress';
@Injectable() @Injectable()
export class CoreMemberAddressService extends BaseService<MemberAddress> { export class CoreMemberAddressService {
constructor( constructor(
@InjectRepository(MemberAddress) @InjectRepository(MemberAddress)
private memberAddressRepository: Repository<MemberAddress>, private memberAddressRepository: Repository<MemberAddress>,
) { ) {}
super(memberAddressRepository);
}
/** /**
* 获取会员地址列表 * 获取会员地址列表
@@ -26,7 +23,7 @@ export class CoreMemberAddressService extends BaseService<MemberAddress> {
where, where,
skip: (page - 1) * limit, skip: (page - 1) * limit,
take: 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 { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { MemberLevel } from '../../entities/MemberLevel'; import { MemberLevel } from '../../entities/MemberLevel';
@Injectable() @Injectable()
export class CoreMemberLevelService extends BaseService<MemberLevel> { export class CoreMemberLevelService {
constructor( constructor(
@InjectRepository(MemberLevel) @InjectRepository(MemberLevel)
private memberLevelRepository: Repository<MemberLevel>, private memberLevelRepository: Repository<MemberLevel>,
) { ) {}
super(memberLevelRepository);
}
/** /**
* 获取会员等级列表 * 获取会员等级列表
@@ -24,7 +21,7 @@ export class CoreMemberLevelService extends BaseService<MemberLevel> {
where, where,
skip: (page - 1) * limit, skip: (page - 1) * limit,
take: 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) { async setDefault(level_id: number) {
// 先取消其他等级的默认状态 // 先取消其他等级的默认状态
await this.memberLevelRepository.update( await this.memberLevelRepository.update(
{ sort: 0 }, { level_id: level_id },
{ sort: 1 } { status: 1 }
); );
// 设置当前等级为默认 // 设置当前等级为默认
const result = await this.memberLevelRepository.update(level_id, { const result = await this.memberLevelRepository.update({ level_id }, {
sort: 0, status: 1,
}); });
return (result.affected || 0) > 0; return (result.affected || 0) > 0;

View File

@@ -1,21 +1,18 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not, Between } from 'typeorm'; import { Repository, Not, Between } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { Member } from '../../entities/Member'; import { Member } from '../../entities/Member';
import { MemberLevel } from '../../entities/MemberLevel'; import { MemberLevel } from '../../entities/MemberLevel';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
@Injectable() @Injectable()
export class CoreMemberService extends BaseService<Member> { export class CoreMemberService {
constructor( constructor(
@InjectRepository(Member) @InjectRepository(Member)
private memberRepository: Repository<Member>, private memberRepository: Repository<Member>,
@InjectRepository(MemberLevel) @InjectRepository(MemberLevel)
private memberLevelRepository: Repository<MemberLevel>, private memberLevelRepository: Repository<MemberLevel>,
) { ) {}
super(memberRepository);
}
/** /**
* 创建会员 * 创建会员
@@ -52,21 +49,28 @@ export class CoreMemberService extends BaseService<Member> {
* 根据ID获取会员 * 根据ID获取会员
*/ */
async getMemberById(memberId: number): Promise<Member | null> { 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> { 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查找会员 * 根据ID查找会员
*/ */
async findById(memberId: number): Promise<Member> { 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) { if (!member) {
throw new NotFoundException('会员不存在'); throw new NotFoundException('会员不存在');
@@ -79,14 +83,18 @@ export class CoreMemberService extends BaseService<Member> {
* 根据用户名查找会员 * 根据用户名查找会员
*/ */
async findByUsername(username: string): Promise<Member | null> { 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> { 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.site_id;
delete updateDto.register_time; 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') @Post('build')
@ApiOperation({ summary: '云编译' }) @ApiOperation({ summary: '云编译' })
async build(@Body() body: CloudBuildDto) { async build(@Body() body: CloudBuildDto) {
const data = await this.service.build(body); return await this.service.build(body);
return { code: 200, message: '编译成功', data };
} }
@Get('buildLog') @Get('buildLog')
@ApiOperation({ summary: '获取云编译日志' }) @ApiOperation({ summary: '获取云编译日志' })
async getBuildLog(@Query() query: CloudLogDto) { async getBuildLog(@Query() query: CloudLogDto) {
const data = await this.service.getBuildLog(query); return await this.service.getBuildLog(query);
return { code: 200, message: '获取成功', data };
} }
@Get('buildTask') @Get('buildTask')
@ApiOperation({ summary: '获取云编译任务' }) @ApiOperation({ summary: '获取云编译任务' })
async getBuildTask() { async getBuildTask() {
const data = await this.service.getBuildTask(); return await this.service.getBuildTask();
return { code: 200, message: '获取成功', data };
} }
@Post('cancelBuild') @Post('cancelBuild')
@ApiOperation({ summary: '取消云编译' }) @ApiOperation({ summary: '取消云编译' })
async cancelBuild() { async cancelBuild() {
const data = await this.service.cancelBuild(); return await this.service.cancelBuild();
return { code: 200, message: '取消成功', data };
} }
@Get('buildStatus') @Get('buildStatus')
@ApiOperation({ summary: '获取编译状态' }) @ApiOperation({ summary: '获取编译状态' })
async getBuildStatus() { async getBuildStatus() {
const data = await this.service.getBuildStatus(); return await this.service.getBuildStatus();
return { code: 200, message: '获取成功', data };
} }
} }

View File

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

View File

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

View File

@@ -1,35 +1,46 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
@Entity('sys_notice_sms_log') @Entity('sys_notice_sms_log')
export class SmsLog extends BaseEntity { export class SmsLog {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; 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; mobile: string;
@Column({ name: 'sms_type', type: 'varchar', length: 32, default: '' }) @Column({ name: 'sms_type', type: 'varchar', length: 32, default: '', comment: '发送关键字(注册、找回密码)' })
sms_type: string; sms_type: string;
@Column({ name: 'key', type: 'varchar', length: 32, default: '' }) @Column({ name: 'key', type: 'varchar', length: 32, default: '', comment: '发送关键字(注册、找回密码)' })
key: string; 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; template_id: string;
@Column({ name: 'content', type: 'text' }) @Column({ name: 'content', type: 'text', comment: '发送内容' })
content: string; content: string;
@Column({ name: 'params', type: 'text' }) @Column({ name: 'params', type: 'text', comment: '数据参数' })
params: any; 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; status: string;
@Column({ name: 'result', type: 'text', nullable: true }) @Column({ name: 'result', type: 'text', nullable: true, comment: '短信结果' })
result: string; result: string;
@Column({ name: 'send_time', type: 'int', default: 0 }) @Column({ name: 'send_time', type: 'int', default: 0, comment: '发送时间' })
send_time: number; 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 { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { SmsLog } from '../../entities/SmsLog'; import { SmsLog } from '../../entities/SmsLog';
import { ISmsConfig, ISmsSendParams } from '../../interfaces/sms.interface'; import { ISmsConfig, ISmsSendParams } from '../../interfaces/sms.interface';
import { SmsStatus } from '../../enums/status.enum'; import { SmsStatus } from '../../enums/status.enum';
@Injectable() @Injectable()
export class CoreSmsService extends BaseService<SmsLog> { export class CoreSmsService {
constructor( constructor(
@InjectRepository(SmsLog) @InjectRepository(SmsLog)
private readonly smsLogRepository: Repository<SmsLog>, private readonly smsLogRepository: Repository<SmsLog>,
) { ) {}
super(smsLogRepository);
}
/** /**
* 发送短信 * 发送短信
@@ -30,7 +27,7 @@ export class CoreSmsService extends BaseService<SmsLog> {
try { try {
// 创建短信日志 // 创建短信日志
savedLog = await this.create({ savedLog = await this.smsLogRepository.save(this.smsLogRepository.create({
site_id, site_id,
mobile, mobile,
sms_type: 'default', sms_type: 'default',
@@ -39,15 +36,15 @@ export class CoreSmsService extends BaseService<SmsLog> {
template_id, template_id,
params: JSON.stringify(params), params: JSON.stringify(params),
status: SmsStatus.SENDING, status: SmsStatus.SENDING,
send_time: this.getCurrentTimestamp(), send_time: Math.floor(Date.now() / 1000),
}); }));
// TODO: 这里应该调用实际的短信服务商API // TODO: 这里应该调用实际的短信服务商API
// 目前先模拟发送成功 // 目前先模拟发送成功
const success = true; const success = true;
// 更新日志状态 // 更新日志状态
await this.update(savedLog.id, { await this.smsLogRepository.update(savedLog.id, {
status: success ? SmsStatus.SUCCESS : SmsStatus.FAILED, status: success ? SmsStatus.SUCCESS : SmsStatus.FAILED,
result: success ? '发送成功' : '发送失败', result: success ? '发送成功' : '发送失败',
}); });
@@ -56,7 +53,7 @@ export class CoreSmsService extends BaseService<SmsLog> {
} catch (error) { } catch (error) {
// 记录失败日志 // 记录失败日志
if (savedLog?.id) { if (savedLog?.id) {
await this.update(savedLog.id, { await this.smsLogRepository.update(savedLog.id, {
status: SmsStatus.FAILED, status: SmsStatus.FAILED,
result: error.message, result: error.message,
}); });
@@ -85,6 +82,6 @@ export class CoreSmsService extends BaseService<SmsLog> {
*/ */
async getSmsLogs(site_id: number, where: any = {}): Promise<SmsLog[]> { async getSmsLogs(site_id: number, where: any = {}): Promise<SmsLog[]> {
const query = { site_id, ...where }; const query = { site_id, ...where };
return await this.findMany(query); return await this.smsLogRepository.find({ where: query });
} }
} }

View File

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

View File

@@ -7,6 +7,8 @@
Query, Query,
UseGuards, UseGuards,
Req, Req,
UnauthorizedException,
NotFoundException,
} from "@nestjs/common"; } from "@nestjs/common";
import { import {
ApiTags, ApiTags,
@@ -42,77 +44,76 @@ export class PayController {
constructor(private readonly payService: PayService) {} constructor(private readonly payService: PayService) {}
@Get("audit/page") @Get("audit/page")
@Roles('admin')
@ApiOperation({ summary: "获取待审核支付记录" }) @ApiOperation({ summary: "获取待审核支付记录" })
@ApiResponse({ status: 200, description: "获取成功" }) @ApiResponse({ status: 200, description: "获取成功" })
async getAuditPage(@Query() query: PayAuditQueryDto, @Req() req: AuthenticatedRequest) { async getAuditPage(@Query() query: PayAuditQueryDto, @Req() req: AuthenticatedRequest) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.payService.getAuditPage(siteId, query); throw new UnauthorizedException('Missing site_id');
return { code: 200, message: "获取成功", data: result };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
} }
return await this.payService.getAuditPage(siteId, query);
} }
@Get(":id") @Get(":id")
@Roles('admin')
@ApiOperation({ summary: "获取支付详情" }) @ApiOperation({ summary: "获取支付详情" })
@ApiParam({ name: "id", description: "支付ID" }) @ApiParam({ name: "id", description: "支付ID" })
@ApiResponse({ status: 200, description: "获取成功" }) @ApiResponse({ status: 200, description: "获取成功" })
async getDetail(@Param("id") id: number, @Req() req: AuthenticatedRequest) { async getDetail(@Param("id") id: number, @Req() req: AuthenticatedRequest) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.payService.getDetail(siteId, id); throw new UnauthorizedException('Missing site_id');
if (!result) {
return { code: 404, message: "支付记录不存在" };
}
return { code: 200, message: "获取成功", data: result };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
} }
const result = await this.payService.getDetail(siteId, id);
if (!result) {
throw new NotFoundException('支付记录不存在');
}
return result;
} }
@Post("pass") @Post("pass")
@Roles('admin')
@ApiOperation({ summary: "支付审核通过" }) @ApiOperation({ summary: "支付审核通过" })
@ApiResponse({ status: 200, description: "审核成功" }) @ApiResponse({ status: 200, description: "审核成功" })
async pass(@Body() payPassDto: PayPassDto, @Req() req: AuthenticatedRequest) { async pass(@Body() payPassDto: PayPassDto, @Req() req: AuthenticatedRequest) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.payService.pass(siteId, payPassDto.outTradeNo); throw new UnauthorizedException('Missing site_id');
if (!result) {
return { code: 404, message: "支付记录不存在" };
}
return { code: 200, message: "审核通过成功" };
} catch (error) {
return { code: 500, message: "审核失败", error: error.message };
} }
const result = await this.payService.pass(siteId, payPassDto.outTradeNo);
if (!result) {
throw new NotFoundException('支付记录不存在');
}
return { success: true, message: '审核通过成功' };
} }
@Post("refuse") @Post("refuse")
@Roles('admin')
@ApiOperation({ summary: "支付审核拒绝" }) @ApiOperation({ summary: "支付审核拒绝" })
@ApiResponse({ status: 200, description: "审核成功" }) @ApiResponse({ status: 200, description: "审核成功" })
async refuse(@Body() payRefuseDto: PayRefuseDto, @Req() req: AuthenticatedRequest) { async refuse(@Body() payRefuseDto: PayRefuseDto, @Req() req: AuthenticatedRequest) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.payService.refuse(siteId, payRefuseDto.outTradeNo, payRefuseDto.reason); throw new UnauthorizedException('Missing site_id');
if (!result) {
return { code: 404, message: "支付记录不存在" };
}
return { code: 200, message: "审核拒绝成功" };
} catch (error) {
return { code: 500, message: "审核失败", error: error.message };
} }
const result = await this.payService.refuse(siteId, payRefuseDto.outTradeNo, payRefuseDto.reason);
if (!result) {
throw new NotFoundException('支付记录不存在');
}
return { success: true, message: '审核拒绝成功' };
} }
@Get("count") @Get("count")
@Roles('admin')
@ApiOperation({ summary: "统计支付数据" }) @ApiOperation({ summary: "统计支付数据" })
@ApiResponse({ status: 200, description: "获取成功" }) @ApiResponse({ status: 200, description: "获取成功" })
async payCount(@Query() query: any, @Req() req: AuthenticatedRequest) { async payCount(@Query() query: any, @Req() req: AuthenticatedRequest) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.payService.payCount(siteId, query); throw new UnauthorizedException('Missing site_id');
return { code: 200, message: "获取成功", data: { count: result } };
} catch (error) {
return { code: 500, message: "获取失败", error: error.message };
} }
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, Query,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard'; import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { PayRefundService } from '../../services/admin/PayRefundService'; import { PayRefundService } from '../../services/admin/PayRefundService';
@@ -21,6 +22,7 @@ export class PayRefundController {
* 退款列表 * 退款列表
*/ */
@Get('lists') @Get('lists')
@Roles('admin')
async lists(@Query() query: any) { async lists(@Query() query: any) {
return this.payRefundService.getPage(query); return this.payRefundService.getPage(query);
} }
@@ -29,6 +31,7 @@ export class PayRefundController {
* 退款信息 * 退款信息
*/ */
@Get('info/:refund_id') @Get('info/:refund_id')
@Roles('admin')
async info(@Param('refund_id') refund_id: string) { async info(@Param('refund_id') refund_id: string) {
return this.payRefundService.getInfo(parseInt(refund_id)); return this.payRefundService.getInfo(parseInt(refund_id));
} }
@@ -37,6 +40,7 @@ export class PayRefundController {
* 创建退款 * 创建退款
*/ */
@Post('create') @Post('create')
@Roles('admin')
async create(@Body() data: { async create(@Body() data: {
pay_id: number; pay_id: number;
refund_amount: number; refund_amount: number;
@@ -51,6 +55,7 @@ export class PayRefundController {
* 处理退款 * 处理退款
*/ */
@Post('process/:refund_id') @Post('process/:refund_id')
@Roles('admin')
async process(@Param('refund_id') refund_id: string) { async process(@Param('refund_id') refund_id: string) {
return this.payRefundService.process(parseInt(refund_id)); return this.payRefundService.process(parseInt(refund_id));
} }
@@ -59,6 +64,7 @@ export class PayRefundController {
* 取消退款 * 取消退款
*/ */
@Post('cancel/:refund_id') @Post('cancel/:refund_id')
@Roles('admin')
async cancel(@Param('refund_id') refund_id: string) { async cancel(@Param('refund_id') refund_id: string) {
return this.payRefundService.cancel(parseInt(refund_id)); return this.payRefundService.cancel(parseInt(refund_id));
} }
@@ -67,6 +73,7 @@ export class PayRefundController {
* 获取退款状态 * 获取退款状态
*/ */
@Get('status/:refund_id') @Get('status/:refund_id')
@Roles('admin')
async getStatus(@Param('refund_id') refund_id: string) { async getStatus(@Param('refund_id') refund_id: string) {
return this.payRefundService.getStatus(parseInt(refund_id)); return this.payRefundService.getStatus(parseInt(refund_id));
} }
@@ -75,6 +82,7 @@ export class PayRefundController {
* 获取退款统计 * 获取退款统计
*/ */
@Get('statistics') @Get('statistics')
@Roles('admin')
async getStatistics(@Query() query: any) { async getStatistics(@Query() query: any) {
return this.payRefundService.getStatistics(query); return this.payRefundService.getStatistics(query);
} }

View File

@@ -8,6 +8,7 @@ import {
Query, Query,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard'; import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { TransferService } from '../../services/admin/TransferService'; import { TransferService } from '../../services/admin/TransferService';
@@ -21,6 +22,7 @@ export class TransferController {
* 转账列表 * 转账列表
*/ */
@Get('lists') @Get('lists')
@Roles('admin')
async lists(@Query() query: any) { async lists(@Query() query: any) {
return this.transferService.getPage(query); return this.transferService.getPage(query);
} }
@@ -29,6 +31,7 @@ export class TransferController {
* 转账信息 * 转账信息
*/ */
@Get('info/:transfer_id') @Get('info/:transfer_id')
@Roles('admin')
async info(@Param('transfer_id') transfer_id: string) { async info(@Param('transfer_id') transfer_id: string) {
return this.transferService.getInfo(parseInt(transfer_id)); return this.transferService.getInfo(parseInt(transfer_id));
} }
@@ -37,6 +40,7 @@ export class TransferController {
* 创建转账 * 创建转账
*/ */
@Post('create') @Post('create')
@Roles('admin')
async create(@Body() data: { async create(@Body() data: {
transfer_type: string; transfer_type: string;
transfer_amount: number; transfer_amount: number;
@@ -52,6 +56,7 @@ export class TransferController {
* 处理转账 * 处理转账
*/ */
@Post('process/:transfer_id') @Post('process/:transfer_id')
@Roles('admin')
async process(@Param('transfer_id') transfer_id: string) { async process(@Param('transfer_id') transfer_id: string) {
return this.transferService.process(parseInt(transfer_id)); return this.transferService.process(parseInt(transfer_id));
} }
@@ -60,6 +65,7 @@ export class TransferController {
* 取消转账 * 取消转账
*/ */
@Post('cancel/:transfer_id') @Post('cancel/:transfer_id')
@Roles('admin')
async cancel(@Param('transfer_id') transfer_id: string) { async cancel(@Param('transfer_id') transfer_id: string) {
return this.transferService.cancel(parseInt(transfer_id)); return this.transferService.cancel(parseInt(transfer_id));
} }
@@ -68,6 +74,7 @@ export class TransferController {
* 获取转账状态 * 获取转账状态
*/ */
@Get('status/:transfer_id') @Get('status/:transfer_id')
@Roles('admin')
async getStatus(@Param('transfer_id') transfer_id: string) { async getStatus(@Param('transfer_id') transfer_id: string) {
return this.transferService.getStatus(parseInt(transfer_id)); return this.transferService.getStatus(parseInt(transfer_id));
} }
@@ -76,6 +83,7 @@ export class TransferController {
* 获取转账统计 * 获取转账统计
*/ */
@Get('statistics') @Get('statistics')
@Roles('admin')
async getStatistics(@Query() query: any) { async getStatistics(@Query() query: any) {
return this.transferService.getStatistics(query); 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 { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
import { BaseEntity } from "../../../core/base/BaseEntity";
/** /**
* 支付实体 * 支付实体
* 对应PHP: app\model\pay\Pay * 对应PHP: app\model\pay\Pay
*/ */
@Entity("pay") @Entity("pay")
export class Pay extends BaseEntity { export class Pay {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ name: "site_id", type: "int", comment: "站点ID" }) @Column({ name: "site_id", type: "int", default: 0, comment: "站点id" })
siteId: number; site_id: number;
@Column({ name: "out_trade_no", length: 100, comment: "商户订单号" }) @Column({ name: "main_id", type: "int", default: 0, comment: "支付会员id" })
outTradeNo: string; main_id: number;
@Column({ name: "trade_type", length: 50, comment: "交易类型" }) @Column({ name: "from_main_id", type: "int", default: 0, comment: "发起支付会员id" })
tradeType: string; from_main_id: number;
@Column({ name: "trade_id", type: "int", default: 0, comment: "交易ID" }) @Column({ name: "out_trade_no", type: "varchar", length: 255, default: "", comment: "支付流水号" })
tradeId: number; out_trade_no: string;
@Column({ name: "trade_no", length: 100, default: "", comment: "交易流水号" }) @Column({ name: "trade_type", type: "varchar", length: 255, default: "", comment: "业务类型" })
tradeNo: string; 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; 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; money: number;
@Column({ name: "voucher", length: 500, default: "", comment: "支付凭证" }) @Column({ name: "voucher", type: "varchar", length: 255, default: "", comment: "支付票据" })
voucher: string; 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; 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; type: string;
@Column({ name: "channel", length: 50, comment: "支付渠道" }) @Column({ name: "channel", type: "varchar", length: 50, default: "", comment: "支付渠道" })
channel: string; channel: string;
@Column({ name: "fail_reason", length: 500, default: "", comment: "失败原因" }) @Column({ name: "fail_reason", type: "varchar", length: 255, default: "", comment: "失败原因" })
failReason: string; fail_reason: string;
@Column({ name: "allow_type", type: "json", nullable: true, comment: "允许的支付方式" }) @Column({ name: "close_time", type: "int", default: 0, comment: "关闭时间" })
allowType: string[]; close_time: number;
@Column({ name: "pay_time", type: "timestamp", nullable: true, comment: "支付时间" }) @Column({ name: "is_del", type: "tinyint", default: 0, comment: "是否删除" })
payTime: Date; is_del: number;
@Column({ name: "close_time", type: "timestamp", nullable: true, comment: "关闭时间" }) @UpdateDateColumn({ name: "update_time", type: "int", default: 0, comment: "更新时间" })
closeTime: Date; update_time: number;
@CreateDateColumn({ name: "create_time", comment: "创建时间" })
createTime: Date;
} }

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 { TypeOrmModule } from "@nestjs/typeorm";
import { Pay } from "./entities/Pay"; import { Pay } from "./entities/Pay";
import { PayChannel } from "./entities/PayChannel"; import { PayChannel } from "./entities/PayChannel";
import { PayTemplate } from "./entities/PayTemplate";
import { PayRefund } from "./entities/PayRefund"; import { PayRefund } from "./entities/PayRefund";
import { PayTransfer } from "./entities/PayTransfer"; import { PayTransfer } from "./entities/PayTransfer";
@@ -15,7 +14,6 @@ import { CorePayTransferService } from "./services/core/CorePayTransferService";
// Admin Services // Admin Services
import { PayService } from "./services/admin/PayService"; import { PayService } from "./services/admin/PayService";
import { PayChannelService } from "./services/admin/PayChannelService"; import { PayChannelService } from "./services/admin/PayChannelService";
import { PayTemplateService } from "./services/admin/PayTemplateService";
import { PayApiService } from "./services/api/PayApiService"; import { PayApiService } from "./services/api/PayApiService";
import { TransferApiService } from "./services/api/TransferApiService"; import { TransferApiService } from "./services/api/TransferApiService";
import { PayRefundService } from "./services/admin/PayRefundService"; import { PayRefundService } from "./services/admin/PayRefundService";
@@ -24,12 +22,13 @@ import { TransferService } from "./services/admin/TransferService";
// Admin Controllers // Admin Controllers
import { PayController } from "./controllers/admin/PayController"; import { PayController } from "./controllers/admin/PayController";
import { PayChannelController } from "./controllers/admin/PayChannelController"; import { PayChannelController } from "./controllers/admin/PayChannelController";
import { PayTemplateController } from "./controllers/admin/PayTemplateController";
import { PayApiController } from "./controllers/api/PayApiController"; import { PayApiController } from "./controllers/api/PayApiController";
import { TransferApiController } from "./controllers/api/TransferApiController"; import { TransferApiController } from "./controllers/api/TransferApiController";
import { PayRefundController } from "./controllers/adminapi/PayRefundController"; import { PayRefundController } from "./controllers/adminapi/PayRefundController";
import { TransferController } from "./controllers/adminapi/TransferController"; import { TransferController } from "./controllers/adminapi/TransferController";
import { JobsModule } from "../../common/jobs/jobs.module"; import { JobsModule } from "../../common/jobs/jobs.module";
import { VendorModule } from "../../vendor";
import { PaymentAdapterRegistry } from "../../vendor/pay/providers/payment.provider";
import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers"; import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
/** /**
@@ -38,8 +37,9 @@ import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
*/ */
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([Pay, PayChannel, PayTemplate, PayRefund, PayTransfer]), TypeOrmModule.forFeature([Pay, PayChannel, PayRefund, PayTransfer]),
JobsModule, forwardRef(() => JobsModule),
VendorModule,
], ],
providers: [ providers: [
// Core Services // Core Services
@@ -51,18 +51,17 @@ import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
// Admin Services // Admin Services
PayService, PayService,
PayChannelService, PayChannelService,
PayTemplateService,
PayRefundService, PayRefundService,
TransferService, TransferService,
PayApiService, PayApiService,
TransferApiService, TransferApiService,
PaymentEventHandlers, PaymentEventHandlers,
PaymentAdapterRegistry,
], ],
controllers: [ controllers: [
// Admin Controllers // Admin Controllers
PayController, PayController,
PayChannelController, PayChannelController,
PayTemplateController,
PayApiController, PayApiController,
TransferApiController, TransferApiController,
PayRefundController, PayRefundController,
@@ -84,6 +83,7 @@ import { PaymentEventHandlers } from "./subscribers/paymentEventHandlers";
// Api Services // Api Services
PayApiService, PayApiService,
TransferApiService, TransferApiService,
PaymentAdapterRegistry,
], ],
}) })
export class PayModule {} 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 { CorePayService } from "../core/CorePayService";
import { CorePayChannelService } from "../core/CorePayChannelService"; import { CorePayChannelService } from "../core/CorePayChannelService";
import { CreatePayDto, RefundDto } from "../../dto/PayDto"; import { CreatePayDto, RefundDto } from "../../dto/PayDto";
@@ -83,17 +83,17 @@ export class PayApiService {
return existed; return existed;
} }
const created = await this.corePayService.add({ const created = await this.corePayService.add({
siteId, site_id: siteId,
outTradeNo: dto.outTradeNo, out_trade_no: dto.outTradeNo,
tradeType: dto.tradeType, trade_type: dto.tradeType,
tradeId: dto.tradeId, trade_id: dto.tradeId,
body: dto.body, body: dto.body,
money: dto.money as any, money: dto.money as any,
voucher: dto.voucher || "", voucher: dto.voucher || "",
status: 0, status: 0,
type: dto.type, type: dto.type,
channel: dto.channel, channel: dto.channel,
allowType: dto.allowType || undefined, // allowType: dto.allowType || undefined, // 字段不存在,注释掉
}); });
const gatewayResp = await adapter.create(config, { const gatewayResp = await adapter.create(config, {
outTradeNo: dto.outTradeNo, outTradeNo: dto.outTradeNo,
@@ -111,7 +111,10 @@ export class PayApiService {
const channel = req.params?.channel || req.query?.channel || req.body?.channel; 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 outTradeNoFromReq = req.query?.outTradeNo || req.body?.out_trade_no || req.body?.outTradeNo;
const payUnsafe = outTradeNoFromReq ? await this.corePayService.findByOutTradeNoUnsafe(outTradeNoFromReq) : null; 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 { adapter, config } = await this.paymentRegistry.resolve(siteId, type, channel);
const result = await adapter.notify(config, { const result = await adapter.notify(config, {
headers: req.headers, headers: req.headers,
@@ -122,7 +125,7 @@ export class PayApiService {
await this.corePayService.updateStatus(siteId, result.outTradeNo, 1, result.tradeNo); await this.corePayService.updateStatus(siteId, result.outTradeNo, 1, result.tradeNo);
const pay = await this.corePayService.findByOutTradeNo(siteId, result.outTradeNo); const pay = await this.corePayService.findByOutTradeNo(siteId, result.outTradeNo);
if (pay) { 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') { } else if (result.status === 'CLOSED') {
await this.corePayService.updateStatus(siteId, result.outTradeNo, 2); await this.corePayService.updateStatus(siteId, result.outTradeNo, 2);
@@ -134,11 +137,11 @@ export class PayApiService {
const pay = await this.corePayService.findByOutTradeNo(siteId, outTradeNo); const pay = await this.corePayService.findByOutTradeNo(siteId, outTradeNo);
if (!pay) return null; if (!pay) return null;
return { return {
outTradeNo: pay.outTradeNo, outTradeNo: pay.out_trade_no,
status: pay.status, status: pay.status,
tradeNo: pay.tradeNo, tradeNo: pay.trade_no,
payTime: pay.payTime, payTime: pay.pay_time,
closeTime: pay.closeTime, closeTime: pay.close_time,
}; };
} }
@@ -159,7 +162,7 @@ export class PayApiService {
refundMoney: body.refundMoney, refundMoney: body.refundMoney,
reason: body.reason, 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; return result;
} }
} }

View File

@@ -1,7 +1,6 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm"; import { InjectRepository } from "@nestjs/typeorm";
import { Repository, Like } from "typeorm"; import { Repository, Like } from "typeorm";
import { BaseService } from "../../../../core/base/BaseService";
import { Pay } from "../../entities/Pay"; import { Pay } from "../../entities/Pay";
/** /**
@@ -9,13 +8,11 @@ import { Pay } from "../../entities/Pay";
* 对应PHP: app\service\core\pay\CorePayService * 对应PHP: app\service\core\pay\CorePayService
*/ */
@Injectable() @Injectable()
export class CorePayService extends BaseService<Pay> { export class CorePayService {
constructor( constructor(
@InjectRepository(Pay) @InjectRepository(Pay)
private readonly payRepository: Repository<Pay>, private readonly payRepository: Repository<Pay>,
) { ) {}
super(payRepository);
}
/** /**
* 分页查询支付记录 * 分页查询支付记录
@@ -29,24 +26,24 @@ export class CorePayService extends BaseService<Pay> {
.where("pay.site_id = :siteId", { siteId }) .where("pay.site_id = :siteId", { siteId })
.select([ .select([
"pay.id", "pay.id",
"pay.outTradeNo", "pay.out_trade_no",
"pay.tradeType", "pay.trade_type",
"pay.tradeId", "pay.trade_id",
"pay.tradeNo", "pay.trade_no",
"pay.body", "pay.body",
"pay.money", "pay.money",
"pay.voucher", "pay.voucher",
"pay.status", "pay.status",
"pay.type", "pay.type",
"pay.channel", "pay.channel",
"pay.failReason", "pay.fail_reason",
"pay.payTime", "pay.pay_time",
"pay.closeTime", "pay.close_time",
"pay.createTime", "pay.create_time",
]); ]);
if (where.outTradeNo) { if (where.outTradeNo) {
queryBuilder.andWhere("pay.outTradeNo LIKE :outTradeNo", { queryBuilder.andWhere("pay.out_trade_no LIKE :outTradeNo", {
outTradeNo: `%${where.outTradeNo}%`, outTradeNo: `%${where.outTradeNo}%`,
}); });
} }
@@ -94,7 +91,7 @@ export class CorePayService extends BaseService<Pay> {
.andWhere("pay.type = :type", { type: "offline" }) .andWhere("pay.type = :type", { type: "offline" })
.select([ .select([
"pay.id", "pay.id",
"pay.outTradeNo", "pay.out_trade_no",
"pay.type", "pay.type",
"pay.money", "pay.money",
"pay.body", "pay.body",
@@ -106,7 +103,7 @@ export class CorePayService extends BaseService<Pay> {
]); ]);
if (where.outTradeNo) { if (where.outTradeNo) {
queryBuilder.andWhere("pay.outTradeNo LIKE :outTradeNo", { queryBuilder.andWhere("pay.out_trade_no LIKE :outTradeNo", {
outTradeNo: `%${where.outTradeNo}%`, outTradeNo: `%${where.outTradeNo}%`,
}); });
} }
@@ -141,23 +138,23 @@ export class CorePayService extends BaseService<Pay> {
*/ */
async getDetail(siteId: number, id: number) { async getDetail(siteId: number, id: number) {
return await this.payRepository.findOne({ return await this.payRepository.findOne({
where: { siteId, id }, where: { site_id: siteId, id },
select: [ select: [
"id", "id",
"outTradeNo", "out_trade_no",
"tradeType", "trade_type",
"tradeId", "trade_id",
"tradeNo", "trade_no",
"body", "body",
"money", "money",
"voucher", "voucher",
"status", "status",
"type", "type",
"channel", "channel",
"failReason", "fail_reason",
"payTime", "pay_time",
"closeTime", "close_time",
"createTime", "create_time",
], ],
}); });
} }
@@ -199,11 +196,11 @@ export class CorePayService extends BaseService<Pay> {
updateData.tradeNo = tradeNo; updateData.tradeNo = tradeNo;
} }
} else if (status === 2) { } else if (status === 2) {
updateData.closeTime = new Date(); updateData.close_time = new Date();
} }
const result = await this.payRepository.update( const result = await this.payRepository.update(
{ siteId, outTradeNo }, { site_id: siteId, out_trade_no: outTradeNo },
updateData, updateData,
); );
return (result.affected || 0) > 0; 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> { async refuse(siteId: number, outTradeNo: string, reason: string): Promise<boolean> {
const result = await this.payRepository.update( const result = await this.payRepository.update(
{ siteId, outTradeNo }, { site_id: siteId, out_trade_no: outTradeNo },
{ {
status: 2, status: 2,
failReason: reason, fail_reason: reason,
closeTime: new Date(), close_time: Math.floor(Date.now() / 1000),
}, },
); );
return (result.affected || 0) > 0; return (result.affected || 0) > 0;
@@ -272,7 +269,7 @@ export class CorePayService extends BaseService<Pay> {
*/ */
async findByOutTradeNo(siteId: number, outTradeNo: string) { async findByOutTradeNo(siteId: number, outTradeNo: string) {
return await this.payRepository.findOne({ 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) { async findByOutTradeNoUnsafe(outTradeNo: string) {
return await this.payRepository.findOne({ 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 const result = await this.payRepository
.createQueryBuilder() .createQueryBuilder()
.update(Pay) .update(Pay)
.set({ status: 2, closeTime: () => 'CURRENT_TIMESTAMP' }) .set({ status: 2, close_time: () => 'CURRENT_TIMESTAMP' })
.where('status = :status', { status: 0 }) .where('status = :status', { status: 0 })
.andWhere('create_time < :before', { before }) .andWhere('create_time < :before', { before })
.execute(); .execute();
@@ -381,7 +378,7 @@ export class CorePayService extends BaseService<Pay> {
.where('pay.site_id = :siteId', { siteId }); .where('pay.site_id = :siteId', { siteId });
if (keyword) { if (keyword) {
query.andWhere('pay.outTradeNo LIKE :keyword', { keyword: `%${keyword}%` }); query.andWhere('pay.out_trade_no LIKE :keyword', { keyword: `%${keyword}%` });
} }
if (status !== '') { if (status !== '') {
@@ -493,10 +490,10 @@ export class CorePayService extends BaseService<Pay> {
*/ */
async manualComplete(siteId: number, outTradeNo: string) { async manualComplete(siteId: number, outTradeNo: string) {
const result = await this.payRepository.update( const result = await this.payRepository.update(
{ site_id: siteId, outTradeNo }, { site_id: siteId, out_trade_no: outTradeNo },
{ {
status: 1, status: 1,
payTime: new Date(), pay_time: Math.floor(Date.now() / 1000),
update_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) { async cancel(siteId: number, outTradeNo: string) {
const result = await this.payRepository.update( const result = await this.payRepository.update(
{ site_id: siteId, outTradeNo }, { site_id: siteId, out_trade_no: outTradeNo },
{ {
status: 2, status: 2,
closeTime: new Date(), close_time: Math.floor(Date.now() / 1000),
update_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 { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
@Entity('sys_role') @Entity('sys_role')
export class SysRole extends BaseEntity { export class SysRole {
@PrimaryGeneratedColumn({ name: 'role_id' }) @PrimaryGeneratedColumn({ name: 'role_id' })
role_id: number; role_id: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
@Column({ @Column({
name: 'role_name', name: 'role_name',
type: 'varchar', type: 'varchar',
@@ -19,7 +21,7 @@ export class SysRole extends BaseEntity {
name: 'rules', name: 'rules',
type: 'text', type: 'text',
nullable: true, nullable: true,
comment: '角色权限(菜单键数组JSON)', comment: '角色权限(menus_id)',
}) })
rules: string; rules: string;
@@ -27,10 +29,16 @@ export class SysRole extends BaseEntity {
name: 'status', name: 'status',
type: 'tinyint', type: 'tinyint',
default: 1, default: 1,
comment: '状态:0=禁用,1=启用', comment: '状态',
}) })
status: 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;
// 业务逻辑方法 - <20>?PHP 项目保持一<E68C81>? // 业务逻辑方法 - <20>?PHP 项目保持一<E68C81>?
getStatusText(): string { getStatusText(): string {
const statusMap: { [key: number]: string } = { 0: '禁用', 1: '正常' }; const statusMap: { [key: number]: string } = { 0: '禁用', 1: '正常' };

View File

@@ -1,31 +1,29 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseService } from '@wwjCore/base/BaseService';
import { SysRole } from '../../entities/SysRole'; import { SysRole } from '../../entities/SysRole';
@Injectable() @Injectable()
export class CoreRoleService extends BaseService<SysRole> { export class CoreRoleService {
constructor( constructor(
@InjectRepository(SysRole) @InjectRepository(SysRole)
private sysRoleRepository: Repository<SysRole>, private sysRoleRepository: Repository<SysRole>,
) { ) {}
super(sysRoleRepository);
}
async createRole(roleData: Partial<SysRole>): Promise<SysRole> { 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> { async getRoleById(role_id: number): Promise<SysRole | null> {
return await this.findOne(role_id); return await this.sysRoleRepository.findOne({ where: { role_id } });
} }
async getRoleByName( async getRoleByName(
role_name: string, role_name: string,
site_id: number, site_id: number,
): Promise<SysRole | null> { ): Promise<SysRole | null> {
return await this.findOneBy({ role_name, site_id }); return await this.sysRoleRepository.findOne({ where: { role_name, site_id } });
} }
async updateRole( async updateRole(
@@ -37,7 +35,7 @@ export class CoreRoleService extends BaseService<SysRole> {
throw new NotFoundException('角色不存在'); throw new NotFoundException('角色不存在');
} }
await this.update(role_id, updateData); await this.sysRoleRepository.update({ role_id }, updateData);
const updatedRole = await this.getRoleById(role_id); const updatedRole = await this.getRoleById(role_id);
if (!updatedRole) { if (!updatedRole) {
throw new NotFoundException('角色更新后不存在'); throw new NotFoundException('角色更新后不存在');
@@ -51,15 +49,15 @@ export class CoreRoleService extends BaseService<SysRole> {
throw new NotFoundException('角色不存在'); throw new NotFoundException('角色不存在');
} }
await this.delete(role_id); await this.sysRoleRepository.delete({ role_id });
} }
async getRolesByAppType(site_id: number): Promise<SysRole[]> { 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[]> { 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( async isRoleNameExists(
@@ -78,9 +76,9 @@ export class CoreRoleService extends BaseService<SysRole> {
} }
async getRoleStats(site_id: number): Promise<any> { async getRoleStats(site_id: number): Promise<any> {
const total = await this.count({ site_id }); const total = await this.sysRoleRepository.count({ where: { site_id } });
const active = await this.count({ site_id, status: 1 }); const active = await this.sysRoleRepository.count({ where: { site_id, status: 1 } });
const inactive = await this.count({ site_id, status: 0 }); const inactive = await this.sysRoleRepository.count({ where: { site_id, status: 0 } });
return { return {
total, 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 { ApiTags, ApiOperation } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -21,17 +21,21 @@ export class SiteAccountLogController {
@Get('page') @Get('page')
@ApiOperation({ summary: '账单分页' }) @ApiOperation({ summary: '账单分页' })
async page(@Query() query: SiteAccountLogQueryDto, @Req() req: AuthenticatedRequest) { async page(@Query() query: SiteAccountLogQueryDto, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.service.getPage(siteId, query); if (!siteId) {
return { code: 200, message: '获取成功', data }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.getPage(siteId, query);
} }
@Post() @Post()
@ApiOperation({ summary: '新增账单' }) @ApiOperation({ summary: '新增账单' })
async add(@Body() body: CreateSiteAccountLogDto, @Req() req: AuthenticatedRequest) { async add(@Body() body: CreateSiteAccountLogDto, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.service.add(siteId, body); if (!siteId) {
return { code: 200, message: '创建成功', data }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.add(siteId, body);
} }
} }

View File

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

View File

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

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 { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -20,18 +20,22 @@ export class SiteUserController {
@Get('page') @Get('page')
@ApiOperation({ summary: '用户分页' }) @ApiOperation({ summary: '用户分页' })
async page(@Query() query: any, @Req() req: AuthenticatedRequest) { async page(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.service.getPage(siteId, query); if (!siteId) {
return { code: 200, message: '获取成功', data }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.getPage(siteId, query);
} }
@Get(':uid') @Get(':uid')
@ApiOperation({ summary: '用户详情' }) @ApiOperation({ summary: '用户详情' })
@ApiParam({ name: 'uid', description: '用户ID' }) @ApiParam({ name: 'uid', description: '用户ID' })
async info(@Param('uid', ParseIntPipe) uid: number, @Req() req: AuthenticatedRequest) { async info(@Param('uid', ParseIntPipe) uid: number, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.service.getInfo(siteId, uid); if (!siteId) {
return { code: 200, message: '获取成功', data }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.service.getInfo(siteId, uid);
} }
@Put(':uid/status/:status') @Put(':uid/status/:status')
@@ -40,8 +44,7 @@ export class SiteUserController {
@Param('uid', ParseIntPipe) uid: number, @Param('uid', ParseIntPipe) uid: number,
@Param('status', ParseIntPipe) status: number, @Param('status', ParseIntPipe) status: number,
) { ) {
const data = await this.service.updateStatus(uid, status); return await this.service.updateStatus(uid, status);
return { code: 200, message: '更新成功', data };
} }
@Post(':uid/roles') @Post(':uid/roles')
@@ -51,9 +54,11 @@ export class SiteUserController {
@Body() body: { role_ids: string }, @Body() body: { role_ids: string },
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.service.assignRoles(siteId, uid, body.role_ids || ''); if (!siteId) {
return { code: 200, message: '更新成功', data }; 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 { ApiTags, ApiOperation } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -20,9 +20,11 @@ export class UserLogController {
@Get('page') @Get('page')
@ApiOperation({ summary: '日志分页' }) @ApiOperation({ summary: '日志分页' })
async page(@Query() query: any, @Req() req: AuthenticatedRequest) { async page(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.service.getPage(siteId, query); if (!siteId) {
return { code: 200, message: '获取成功', data }; 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 { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";
import { BaseEntity } from "../../../core/base/BaseEntity";
/** /**
* 站点实体 * 站点实体
* 对应PHP: app\model\site\Site * 对应PHP: app\model\site\Site
*/ */
@Entity("site") @Entity("site")
export class Site extends BaseEntity { export class Site {
@PrimaryGeneratedColumn({ name: "site_id" }) @PrimaryGeneratedColumn({ name: "site_id" })
siteId: number; site_id: number;
@Column({ name: "site_name", length: 100, comment: "站点名称" }) @Column({ name: "site_name", type: "varchar", length: 50, default: "", comment: "站点名称" })
siteName: string; site_name: string;
@Column({ name: "front_end_name", length: 100, default: "", comment: "前端名称" }) @Column({ name: "group_id", type: "int", default: 0, comment: "分组ID(0:不限制)" })
frontEndName: string; group_id: number;
@Column({ name: "front_end_logo", length: 500, default: "", comment: "前端Logo" }) @Column({ name: "keywords", type: "varchar", length: 255, default: "", comment: "关键字" })
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: "关键词" })
keywords: string; 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; logo: string;
@Column({ name: "icon", length: 500, default: "", comment: "图标" }) @Column({ name: "desc", type: "varchar", length: 255, default: "", comment: "简介" })
icon: string;
@Column({ name: "desc", type: "text", default: "", comment: "描述" })
desc: string; 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; status: number;
@Column({ name: "latitude", type: "decimal", precision: 10, scale: 6, default: 0, comment: "纬度" }) @Column({ name: "latitude", type: "varchar", length: 255, default: "", comment: "纬度" })
latitude: number; latitude: string;
@Column({ name: "longitude", type: "decimal", precision: 10, scale: 6, default: 0, comment: "经度" }) @Column({ name: "longitude", type: "varchar", length: 255, default: "", comment: "经度" })
longitude: number; longitude: string;
@Column({ name: "province_id", type: "int", default: 0, comment: "省份ID" }) @Column({ name: "province_id", type: "int", default: 0, comment: "省" })
provinceId: number; province_id: number;
@Column({ name: "city_id", type: "int", default: 0, comment: "城市ID" }) @Column({ name: "city_id", type: "int", default: 0, comment: "" })
cityId: number; city_id: number;
@Column({ name: "district_id", type: "int", default: 0, comment: "区县ID" }) @Column({ name: "district_id", type: "int", default: 0, comment: "区" })
districtId: number; district_id: number;
@Column({ name: "address", length: 200, default: "", comment: "地址" }) @Column({ name: "address", type: "varchar", length: 255, default: "", comment: "详细地址" })
address: string; address: string;
@Column({ name: "full_address", length: 500, default: "", comment: "完整地址" }) @Column({ name: "full_address", type: "varchar", length: 255, default: "", comment: "完整地址" })
fullAddress: string; full_address: string;
@Column({ name: "phone", length: 20, default: "", comment: "电话" }) @Column({ name: "phone", type: "varchar", length: 255, default: "", comment: "客服电话" })
phone: string; phone: string;
@Column({ name: "business_hours", length: 200, default: "", comment: "营业时间" }) @Column({ name: "business_hours", type: "varchar", length: 255, default: "", comment: "营业时间" })
businessHours: string; business_hours: string;
@Column({ name: "expire_time", type: "timestamp", nullable: true, comment: "过期时间" }) @CreateDateColumn({ name: "create_time", type: "int", default: 0, comment: "创建时间" })
expireTime: Date; create_time: number;
@Column({ name: "group_id", type: "int", default: 0, comment: "分组ID" }) @Column({ name: "expire_time", type: "bigint", default: 0, comment: "到期时间如果是0 无限期)" })
groupId: number; expire_time: number;
@Column({ name: "app", type: "json", nullable: true, comment: "应用配置" }) @Column({ name: "front_end_name", type: "varchar", length: 50, default: "", comment: "前台名称" })
app: string[]; front_end_name: string;
@Column({ name: "addons", type: "json", nullable: true, comment: "插件配置" }) @Column({ name: "front_end_logo", type: "varchar", length: 255, default: "", comment: "前台logo" })
addons: string[]; front_end_logo: string;
@Column({ name: "initalled_addon", type: "json", nullable: true, comment: "已安装插件" }) @Column({ name: "front_end_icon", type: "varchar", length: 255, default: "", comment: "前台图标" })
initalledAddon: string[]; front_end_icon: string;
@Column({ name: "site_domain", length: 200, default: "", comment: "站点域名" }) @Column({ name: "icon", type: "varchar", length: 255, default: "", comment: "图标" })
siteDomain: string; icon: string;
@Column({ name: "meta_title", length: 200, default: "", comment: "SEO标题" }) @Column({ name: "member_no", type: "varchar", length: 50, default: "0", comment: "会员编号" })
metaTitle: string; member_no: string;
@Column({ name: "meta_desc", length: 500, default: "", comment: "SEO描述" }) @Column({ name: "app", type: "text", default: "", comment: "应用" })
metaDesc: string; app: string;
@Column({ name: "meta_keyword", length: 200, default: "", comment: "SEO关键词" }) @Column({ name: "addons", type: "text", default: "", comment: "插件" })
metaKeyword: string; addons: string;
@CreateDateColumn({ name: "create_time", comment: "创建时间" }) @Column({ name: "initalled_addon", type: "text", default: "", comment: "已安装插件" })
createTime: Date; initalled_addon: string;
@UpdateDateColumn({ name: "update_time", comment: "更新时间" }) @Column({ name: "site_domain", type: "varchar", length: 255, default: "", comment: "站点域名" })
updateTime: Date; 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'; import { BaseEntity } from '../../../core/base/BaseEntity';
/** /**
@@ -7,6 +7,8 @@ import { BaseEntity } from '../../../core/base/BaseEntity';
*/ */
@Entity('site_account_log') @Entity('site_account_log')
export class SiteAccountLog extends BaseEntity { export class SiteAccountLog extends BaseEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ name: 'type', type: 'varchar', length: 50, comment: '类型pay支付refund退款transfer转账' }) @Column({ name: 'type', type: 'varchar', length: 50, comment: '类型pay支付refund退款transfer转账' })
type: string; type: string;

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,10 @@ export class SiteGroupCoreService {
return this.repo.findOne({ where: { group_id } }); return this.repo.findOne({ where: { group_id } });
} }
async getAll() {
return this.repo.find();
}
async add(payload: Partial<SiteGroup>) { async add(payload: Partial<SiteGroup>) {
const entity = this.repo.create({ ...payload, create_time: Math.floor(Date.now() / 1000) }); const entity = this.repo.create({ ...payload, create_time: Math.floor(Date.now() / 1000) });
return this.repo.save(entity); return this.repo.save(entity);
@@ -47,4 +51,9 @@ export class SiteGroupCoreService {
const list = await this.repo.find(); const list = await this.repo.find();
return list.map((g) => ({ id: (g as any).group_id, label: g.group_name, children: [] })); 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 { Module } from "@nestjs/common";
import { CacheModule } from "@nestjs/cache-manager";
import { TypeOrmModule } from "@nestjs/typeorm"; import { TypeOrmModule } from "@nestjs/typeorm";
import { Site } from "./entities/Site"; import { Site } from "./entities/Site";
import { SiteGroup } from "./entities/SiteGroup"; import { SiteGroup } from "./entities/SiteGroup";
@@ -6,6 +7,8 @@ import { SiteAccountLog } from "./entities/SiteAccountLog";
import { SiteAccount } from "./entities/SiteAccount"; import { SiteAccount } from "./entities/SiteAccount";
import { SysUserLog } from "./entities/SysUserLog"; import { SysUserLog } from "./entities/SysUserLog";
import { DiyModule } from "../diy/diy.module"; import { DiyModule } from "../diy/diy.module";
import { SysUser } from "../admin/entities/SysUser";
import { SysUserRole } from "../admin/entities/SysUserRole";
// Core Services // Core Services
import { CoreSiteService } from "./services/core/CoreSiteService"; import { CoreSiteService } from "./services/core/CoreSiteService";
@@ -33,8 +36,9 @@ import { DiyLoadEventHandler } from "./subscribers/diyLoadEventHandler";
*/ */
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([Site, SiteGroup, SiteAccountLog, SysUserLog, SiteAccount]), TypeOrmModule.forFeature([Site, SiteGroup, SiteAccountLog, SysUserLog, SiteAccount, SysUser, SysUserRole]),
DiyModule, DiyModule,
CacheModule.register(),
], ],
providers: [ providers: [
// Core Services // Core Services

View File

@@ -17,7 +17,7 @@ export class DiyLoadEventHandler {
@EventHandler('site.diy.load', { consumerGroup: 'site-diy-initializer' }) @EventHandler('site.diy.load', { consumerGroup: 'site-diy-initializer' })
async onLoad(event: DomainEvent): Promise<void> { async onLoad(event: DomainEvent): Promise<void> {
const data = (event?.data || {}) as { site_id?: number; main_app?: string[]; tag?: 'add' | 'update' }; 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; if (!siteId) return;
try { 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 { import {
ApiTags, ApiTags,
ApiOperation, ApiOperation,
@@ -42,9 +42,11 @@ export class AppController {
@Query('type') type?: string, @Query('type') type?: string,
@Req() req?: AuthenticatedRequest, @Req() req?: AuthenticatedRequest,
) { ) {
const siteId = req?.user?.siteId || 0; const siteId = req?.user?.siteId;
const result = await this.appService.getAppList(); if (!siteId) {
return { code: 200, message: '获取成功', data: result }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.appService.getAppList();
} }
@Get(':appKey') @Get(':appKey')
@@ -55,9 +57,11 @@ export class AppController {
@Param('appKey') appKey: string, @Param('appKey') appKey: string,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.appService.getAppInfo(appKey); if (!siteId) {
return { code: 200, message: '获取成功', data: result }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.appService.getAppInfo(appKey);
} }
@Get('check/:appKey') @Get('check/:appKey')

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ export class ChannelController {
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) { async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
// TODO: 实现渠道分页列表 // TODO: 实现渠道分页列表
return { code: 200, message: '获取成功', data: { list: [], total: 0 } }; return { list: [], total: 0 };
} }
@Get('list') @Get('list')
@@ -50,7 +50,7 @@ export class ChannelController {
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) { async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
// TODO: 实现渠道列表 // TODO: 实现渠道列表
return { code: 200, message: '获取成功', data: [] }; return [];
} }
@Get(':id') @Get(':id')
@@ -62,7 +62,7 @@ export class ChannelController {
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
// TODO: 实现渠道详情 // TODO: 实现渠道详情
return { code: 200, message: '获取成功', data: null }; return null;
} }
@Post() @Post()
@@ -70,7 +70,7 @@ export class ChannelController {
@ApiResponse({ status: 200, description: '创建成功' }) @ApiResponse({ status: 200, description: '创建成功' })
async add(@Body() data: any, @Req() req: AuthenticatedRequest) { async add(@Body() data: any, @Req() req: AuthenticatedRequest) {
// TODO: 实现渠道新增 // TODO: 实现渠道新增
return { code: 200, message: '创建成功', data: null }; return null;
} }
@Put(':id') @Put(':id')
@@ -83,7 +83,7 @@ export class ChannelController {
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
// TODO: 实现渠道编辑 // TODO: 实现渠道编辑
return { code: 200, message: '更新成功', data: null }; return null;
} }
@Delete(':id') @Delete(':id')
@@ -95,6 +95,6 @@ export class ChannelController {
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
// TODO: 实现渠道删除 // 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 { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -29,31 +29,47 @@ export class CommonController {
@ApiOperation({ summary: '获取字典数据' }) @ApiOperation({ summary: '获取字典数据' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getDict(@Req() req: AuthenticatedRequest) { async getDict(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现字典数据获取 // TODO: 实现字典数据获取
return { code: 200, message: '获取成功', data: {} }; return {};
} }
@Get('config') @Get('config')
@ApiOperation({ summary: '获取系统配置' }) @ApiOperation({ summary: '获取系统配置' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getConfig(@Req() req: AuthenticatedRequest) { async getConfig(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现系统配置获取 // TODO: 实现系统配置获取
return { code: 200, message: '获取成功', data: {} }; return {};
} }
@Post('upload') @Post('upload')
@ApiOperation({ summary: '文件上传' }) @ApiOperation({ summary: '文件上传' })
@ApiResponse({ status: 200, description: '上传成功' }) @ApiResponse({ status: 200, description: '上传成功' })
async upload(@Req() req: AuthenticatedRequest) { async upload(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现文件上传 // TODO: 实现文件上传
return { code: 200, message: '上传成功', data: null }; return null;
} }
@Get('captcha') @Get('captcha')
@ApiOperation({ summary: '获取验证码' }) @ApiOperation({ summary: '获取验证码' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getCaptcha(@Req() req: AuthenticatedRequest) { async getCaptcha(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现验证码获取 // 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 { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -23,10 +23,10 @@ interface AuthenticatedRequest extends Request {
/** /**
* 系统配置控制器 - 管理端 * 系统配置控制器 - 管理端
* 路由前缀: /adminapi/sys/config * 路由前缀: /admin/sys/config
*/ */
@ApiTags('系统配置管理') @ApiTags('系统配置管理')
@Controller('adminapi/sys/config') @Controller('admin/sys/config')
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
export class ConfigController { export class ConfigController {
@@ -36,13 +36,11 @@ export class ConfigController {
@ApiOperation({ summary: '获取版权信息' }) @ApiOperation({ summary: '获取版权信息' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getCopyright(@Req() req: AuthenticatedRequest) { async getCopyright(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.configService.getCopyright(siteId); if (!siteId) {
return { throw new UnauthorizedException('未授权访问:缺少 site_id');
code: 200, }
message: '获取成功', return await this.configService.getCopyright(siteId);
data,
};
} }
@Post('copyright') @Post('copyright')
@@ -52,26 +50,22 @@ export class ConfigController {
@Body() copyrightDto: CopyrightDto, @Body() copyrightDto: CopyrightDto,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.configService.setCopyright(siteId, copyrightDto); if (!siteId) {
return { throw new UnauthorizedException('未授权访问:缺少 site_id');
code: 200, }
message: '设置成功', return await this.configService.setCopyright(siteId, copyrightDto);
data: result,
};
} }
@Get('website') @Get('website')
@ApiOperation({ summary: '获取网站信息' }) @ApiOperation({ summary: '获取网站信息' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getWebSite(@Req() req: AuthenticatedRequest) { async getWebSite(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.configService.getWebSite(siteId); if (!siteId) {
return { throw new UnauthorizedException('未授权访问:缺少 site_id');
code: 200, }
message: '获取成功', return await this.configService.getWebSite(siteId);
data,
};
} }
@Post('website') @Post('website')
@@ -81,26 +75,22 @@ export class ConfigController {
@Body() websiteDto: WebSiteDto, @Body() websiteDto: WebSiteDto,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.configService.setWebSite(siteId, websiteDto); if (!siteId) {
return { throw new UnauthorizedException('未授权访问:缺少 site_id');
code: 200, }
message: '设置成功', return await this.configService.setWebSite(siteId, websiteDto);
data: result,
};
} }
@Get('scene-domain') @Get('scene-domain')
@ApiOperation({ summary: '获取场景域名配置' }) @ApiOperation({ summary: '获取场景域名配置' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getSceneDomain(@Req() req: AuthenticatedRequest) { async getSceneDomain(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.configService.getSceneDomain(siteId); if (!siteId) {
return { throw new UnauthorizedException('未授权访问:缺少 site_id');
code: 200, }
message: '获取成功', return await this.configService.getSceneDomain(siteId);
data,
};
} }
@Post('scene-domain') @Post('scene-domain')
@@ -110,29 +100,25 @@ export class ConfigController {
@Body() sceneDomainDto: SceneDomainDto, @Body() sceneDomainDto: SceneDomainDto,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.configService.setSceneDomain( if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.configService.setSceneDomain(
siteId, siteId,
sceneDomainDto, sceneDomainDto,
); );
return {
code: 200,
message: '设置成功',
data: result,
};
} }
@Get('service') @Get('service')
@ApiOperation({ summary: '获取服务配置' }) @ApiOperation({ summary: '获取服务配置' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getService(@Req() req: AuthenticatedRequest) { async getService(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const data = await this.configService.getService(siteId); if (!siteId) {
return { throw new UnauthorizedException('未授权访问:缺少 site_id');
code: 200, }
message: '获取成功', return await this.configService.getService(siteId);
data,
};
} }
@Post('service') @Post('service')
@@ -142,12 +128,10 @@ export class ConfigController {
@Body() serviceDto: ServiceDto, @Body() serviceDto: ServiceDto,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.configService.setService(siteId, serviceDto); if (!siteId) {
return { throw new UnauthorizedException('未授权访问:缺少 site_id');
code: 200, }
message: '设置成功', return await this.configService.setService(siteId, serviceDto);
data: result,
};
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@
UseGuards, UseGuards,
Req, Req,
ParseIntPipe, ParseIntPipe,
UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
@@ -42,18 +43,22 @@ export class ScheduleController {
@ApiOperation({ summary: '获取定时任务分页列表' }) @ApiOperation({ summary: '获取定时任务分页列表' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) { async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.scheduleService.getPage(siteId, query); if (!siteId) {
return { code: 200, message: '获取成功', data: result }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.getPage(siteId, query);
} }
@Get('list') @Get('list')
@ApiOperation({ summary: '获取定时任务列表' }) @ApiOperation({ summary: '获取定时任务列表' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) { async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.scheduleService.getList(siteId, query); if (!siteId) {
return { code: 200, message: '获取成功', data: result }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.getList(siteId, query);
} }
@Get(':id') @Get(':id')
@@ -64,22 +69,22 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.scheduleService.getInfo(siteId, id); if (!siteId) {
return { code: 200, message: '获取成功', data: result }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.scheduleService.getInfo(siteId, id);
} }
@Post() @Post()
@ApiOperation({ summary: '新增定时任务' }) @ApiOperation({ summary: '新增定时任务' })
@ApiResponse({ status: 200, description: '创建成功' }) @ApiResponse({ status: 200, description: '创建成功' })
async add(@Body() data: any, @Req() req: AuthenticatedRequest) { async add(@Body() data: any, @Req() req: AuthenticatedRequest) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.scheduleService.add(siteId, data); throw new UnauthorizedException('未授权访问:缺少 site_id');
return { code: 200, message: '创建成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '创建失败', data: null };
} }
return await this.scheduleService.add(siteId, data);
} }
@Put(':id') @Put(':id')
@@ -91,13 +96,11 @@ export class ScheduleController {
@Body() data: any, @Body() data: any,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.scheduleService.edit(siteId, id, data); throw new UnauthorizedException('未授权访问:缺少 site_id');
return { code: 200, message: '更新成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '更新失败', data: null };
} }
return await this.scheduleService.edit(siteId, id, data);
} }
@Put(':id/start') @Put(':id/start')
@@ -108,13 +111,11 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.scheduleService.start(siteId, id); throw new UnauthorizedException('未授权访问:缺少 site_id');
return { code: 200, message: '启动成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '启动失败', data: null };
} }
return await this.scheduleService.start(siteId, id);
} }
@Put(':id/stop') @Put(':id/stop')
@@ -125,13 +126,11 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.scheduleService.stop(siteId, id); throw new UnauthorizedException('未授权访问:缺少 site_id');
return { code: 200, message: '停止成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '停止失败', data: null };
} }
return await this.scheduleService.stop(siteId, id);
} }
@Delete(':id') @Delete(':id')
@@ -142,12 +141,10 @@ export class ScheduleController {
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Req() req: AuthenticatedRequest, @Req() req: AuthenticatedRequest,
) { ) {
try { const siteId = req.user?.siteId;
const siteId = req.user?.siteId || 0; if (!siteId) {
const result = await this.scheduleService.del(siteId, id); throw new UnauthorizedException('未授权访问:缺少 site_id');
return { code: 200, message: '删除成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '删除失败', data: null };
} }
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 { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -29,27 +29,35 @@ export class ScheduleLogController {
@ApiOperation({ summary: '获取定时任务日志分页列表' }) @ApiOperation({ summary: '获取定时任务日志分页列表' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) { async getPage(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现定时任务日志分页列表 // TODO: 实现定时任务日志分页列表
return { code: 200, message: '获取成功', data: { list: [], total: 0 } }; return { list: [], total: 0 };
} }
@Get('list') @Get('list')
@ApiOperation({ summary: '获取定时任务日志列表' }) @ApiOperation({ summary: '获取定时任务日志列表' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getList(@Query() query: any, @Req() req: AuthenticatedRequest) { async getList(@Query() query: any, @Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现定时任务日志列表 // TODO: 实现定时任务日志列表
return { code: 200, message: '获取成功', data: [] }; return [];
} }
@Get('stats') @Get('stats')
@ApiOperation({ summary: '获取定时任务统计信息' }) @ApiOperation({ summary: '获取定时任务统计信息' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getStats(@Req() req: AuthenticatedRequest) { async getStats(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId;
if (!siteId) {
throw new UnauthorizedException('未授权访问:缺少 site_id');
}
// TODO: 实现定时任务统计信息 // TODO: 实现定时任务统计信息
return { return { total: 0, success: 0, failed: 0 };
code: 200,
message: '获取成功',
data: { 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 { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import type { Request } from 'express'; import type { Request } from 'express';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard'; import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
@@ -17,10 +17,10 @@ interface AuthenticatedRequest extends Request {
/** /**
* 系统信息管理控制器 - 管理端 * 系统信息管理控制器 - 管理端
* 路由前缀: /adminapi/sys/system * 路由前缀: /admin/sys/system
*/ */
@ApiTags('系统信息') @ApiTags('系统信息')
@Controller('adminapi/sys/system') @Controller('admin/sys/system')
@UseGuards(JwtAuthGuard, RolesGuard) @UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin') @Roles('admin')
export class SystemController { export class SystemController {
@@ -30,52 +30,45 @@ export class SystemController {
@ApiOperation({ summary: '获取系统信息' }) @ApiOperation({ summary: '获取系统信息' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getInfo(@Req() req: AuthenticatedRequest) { async getInfo(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.getInfo(); return await this.systemService.getInfo();
return { code: 200, message: '获取成功', data: result };
} }
@Get('url') @Get('url')
@ApiOperation({ summary: '获取系统URL信息' }) @ApiOperation({ summary: '获取系统URL信息' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getUrl(@Req() req: AuthenticatedRequest) { async getUrl(@Req() req: AuthenticatedRequest) {
const siteId = req.user?.siteId || 0; const siteId = req.user?.siteId;
const result = await this.systemService.getUrl(siteId); if (!siteId) {
return { code: 200, message: '获取成功', data: result }; throw new UnauthorizedException('未授权访问:缺少 site_id');
}
return await this.systemService.getUrl(siteId);
} }
@Get('stats') @Get('stats')
@ApiOperation({ summary: '获取系统统计信息' }) @ApiOperation({ summary: '获取系统统计信息' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getSystemStats(@Req() req: AuthenticatedRequest) { async getSystemStats(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.getSystemStats(); return await this.systemService.getSystemStats();
return { code: 200, message: '获取成功', data: result };
} }
@Get('system-info') @Get('system-info')
@ApiOperation({ summary: '获取详细系统信息' }) @ApiOperation({ summary: '获取详细系统信息' })
@ApiResponse({ status: 200, description: '获取成功' }) @ApiResponse({ status: 200, description: '获取成功' })
async getSystemInfo(@Req() req: AuthenticatedRequest) { async getSystemInfo(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.getSystemInfo(); return await this.systemService.getSystemInfo();
return { code: 200, message: '获取成功', data: result };
} }
@Post('clear-cache') @Post('clear-cache')
@ApiOperation({ summary: '清理系统缓存' }) @ApiOperation({ summary: '清理系统缓存' })
@ApiResponse({ status: 200, description: '清理成功' }) @ApiResponse({ status: 200, description: '清理成功' })
async clearCache(@Req() req: AuthenticatedRequest) { async clearCache(@Req() req: AuthenticatedRequest) {
try { return await this.systemService.clearCache();
const result = await this.systemService.clearCache();
return { code: 200, message: '清理成功', data: result };
} catch (error) {
return { code: 400, message: error.message || '清理失败', data: null };
}
} }
@Get('check-config') @Get('check-config')
@ApiOperation({ summary: '检查系统配置' }) @ApiOperation({ summary: '检查系统配置' })
@ApiResponse({ status: 200, description: '检查完成' }) @ApiResponse({ status: 200, description: '检查完成' })
async checkSystemConfig(@Req() req: AuthenticatedRequest) { async checkSystemConfig(@Req() req: AuthenticatedRequest) {
const result = await this.systemService.checkSystemConfig(); return await this.systemService.checkSystemConfig();
return { code: 200, message: '检查完成', data: result };
} }
} }

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -1,8 +1,7 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
@Entity('sys_attachment') @Entity('sys_attachment')
export class SysAttachment extends BaseEntity { export class SysAttachment {
@PrimaryGeneratedColumn({ name: 'att_id' }) @PrimaryGeneratedColumn({ name: 'att_id' })
att_id: number; att_id: number;
@@ -81,6 +80,15 @@ export class SysAttachment extends BaseEntity {
}) })
storage_type: string; 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 { getFileExtension(): string {
return this.real_name.split('.').pop() || ''; return this.real_name.split('.').pop() || '';

View File

@@ -1,44 +1,49 @@
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/** /**
* 系统配置实体 * 系统配置实体
* 对应数据表: sys_config * 对应数据表: sys_config
*/ */
@Entity('sys_config') @Entity('sys_config')
@Index(['siteId', 'key'], { unique: true }) @Index(['site_id', 'config_key'], { unique: true })
export class SysConfig extends BaseEntity { export class SysConfig {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点ID' }) @Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
siteId: number; site_id: number;
@Column({ name: 'key', type: 'varchar', length: 100, comment: '配置' }) @Column({ name: 'config_key', type: 'varchar', length: 255, default: '', comment: '配置项关键字' })
key: string; config_key: string;
@Column({ @Column({
name: 'value', name: 'value',
type: 'text', type: 'text',
nullable: true, nullable: true,
comment: '配置值(JSON格式)', comment: '配置值json',
}) })
value: string; value: string;
@Column({ @Column({
name: 'desc', name: 'status',
type: 'varchar',
length: 255,
nullable: true,
comment: '配置描述',
})
desc?: string;
@Column({
name: 'is_use',
type: 'tinyint', type: 'tinyint',
default: 1, 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, UpdateDateColumn,
DeleteDateColumn, DeleteDateColumn,
} from 'typeorm'; } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/** /**
* 系统菜单实体 * 系统菜单实体
*/ */
@Entity('sys_menu') @Entity('sys_menu')
export class SysMenu extends BaseEntity { export class SysMenu {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ name: 'menu_name', length: 100, comment: '菜单名称' }) @Column({ name: 'menu_name', type: 'varchar', length: 100, default: '', comment: '菜单名称' })
menuName: string; menu_name: string;
@Column({ @Column({
name: 'menu_short_name', name: 'menu_short_name',
type: 'varchar',
length: 50, length: 50,
nullable: true, nullable: true,
comment: '菜单简称', comment: '菜单简称',
}) })
menuShortName: string; menu_short_name: string;
@Column({ @Column({
name: 'menu_type', name: 'menu_type',
@@ -33,34 +33,37 @@ export class SysMenu extends BaseEntity {
default: 1, default: 1,
comment: '菜单类型 1目录 2菜单 3按钮', comment: '菜单类型 1目录 2菜单 3按钮',
}) })
menuType: number; menu_type: number;
@Column({ name: 'parent_id', type: 'int', default: 0, comment: '父级菜单ID' }) @Column({ name: 'parent_id', type: 'int', default: 0, comment: '父级菜单ID' })
parentId: number; parent_id: number;
@Column({ @Column({
name: 'menu_key', name: 'menu_key',
type: 'varchar',
length: 100, length: 100,
nullable: true, nullable: true,
comment: '菜单标识', comment: '菜单标识',
}) })
menuKey: string; menu_key: string;
@Column({ @Column({
name: 'menu_url', name: 'menu_url',
type: 'varchar',
length: 255, length: 255,
nullable: true, nullable: true,
comment: '菜单链接', comment: '菜单链接',
}) })
menuUrl: string; menu_url: string;
@Column({ @Column({
name: 'menu_icon', name: 'menu_icon',
type: 'varchar',
length: 100, length: 100,
nullable: true, nullable: true,
comment: '菜单图标', comment: '菜单图标',
}) })
menuIcon: string; menu_icon: string;
@Column({ name: 'sort', type: 'int', default: 0, comment: '排序' }) @Column({ name: 'sort', type: 'int', default: 0, comment: '排序' })
sort: number; sort: number;
@@ -79,7 +82,7 @@ export class SysMenu extends BaseEntity {
default: 1, default: 1,
comment: '是否显示 0不显示 1显示', comment: '是否显示 0不显示 1显示',
}) })
isShow: number; is_show: number;
@Column({ @Column({
name: 'is_del', name: 'is_del',
@@ -87,16 +90,19 @@ export class SysMenu extends BaseEntity {
default: 0, default: 0,
comment: '是否删除 0未删除 1已删除', comment: '是否删除 0未删除 1已删除',
}) })
isDel: number; is_del: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' }) @CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '创建时间' })
createTime: Date; create_time: number;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' }) @UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '更新时间' })
updateTime: Date; update_time: number;
@DeleteDateColumn({ name: 'delete_time', comment: '删除时间' }) @DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
deleteTime: Date; delete_time: number;
@Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
site_id: number;
// 虚拟字段 // 虚拟字段
statusName?: string; statusName?: string;

View File

@@ -5,41 +5,48 @@
CreateDateColumn, CreateDateColumn,
UpdateDateColumn, UpdateDateColumn,
} from 'typeorm'; } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/** /**
* 系统通知实体 * 系统通知实体
*/ */
@Entity('sys_notice') @Entity('sys_notice')
export class SysNotice extends BaseEntity { export class SysNotice {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ name: 'title', length: 200, comment: '通知标题' }) @Column({ name: 'site_id', type: 'int', default: 0, comment: '站点ID' })
title: string; site_id: number;
@Column({ name: 'content', type: 'text', comment: '通知内容' }) @Column({ name: 'key', type: 'varchar', length: 50, default: '', comment: '标识' })
content: string; key: string;
@Column({ @Column({ name: 'sms_content', type: 'text', nullable: true, comment: '短信配置参数' })
name: 'type', sms_content: string;
type: 'tinyint',
default: 1,
comment: '通知类型 1系统通知 2用户通知',
})
type: number;
@Column({ @Column({ name: 'is_wechat', type: 'tinyint', default: 0, comment: '公众号模板消息0关闭1开启' })
name: 'status', is_wechat: number;
type: 'tinyint',
default: 1,
comment: '状态 0禁用 1启用',
})
status: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' }) @Column({ name: 'is_weapp', type: 'tinyint', default: 0, comment: '小程序订阅消息0关闭1开启' })
createTime: Date; is_weapp: number;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' }) @Column({ name: 'is_sms', type: 'tinyint', default: 0, comment: '发送短信0关闭1开启' })
updateTime: Date; 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, Column,
CreateDateColumn, CreateDateColumn,
UpdateDateColumn, UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm'; } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/** /**
* 系统角色实体 * 系统角色实体
*/ */
@Entity('sys_role') @Entity('sys_role')
export class SysRole extends BaseEntity { export class SysRole {
@PrimaryGeneratedColumn({ name: 'role_id' }) @PrimaryGeneratedColumn({ name: 'role_id' })
roleId: number; role_id: number;
@Column({ name: 'role_name', length: 50, comment: '角色名称' }) @Column({ name: 'site_id', type: 'int', default: 0, comment: '站点id' })
roleName: string; site_id: number;
@Column({ name: 'role_key', length: 50, comment: '角色标识' }) @Column({ name: 'role_name', type: 'varchar', length: 255, default: '', comment: '角色名称' })
roleKey: string; role_name: string;
@Column({ name: 'sort', type: 'int', default: 0, comment: '排序' }) @Column({ name: 'rules', type: 'text', nullable: true, comment: '角色权限(menus_id)' })
sort: number; rules: string;
@Column({ @Column({ name: 'status', type: 'tinyint', default: 1, comment: '状态' })
name: 'status',
type: 'tinyint',
default: 1,
comment: '状态 0禁用 1启用',
})
status: number; status: number;
@Column({ name: 'remark', length: 500, nullable: true, comment: '备注' }) @CreateDateColumn({ name: 'create_time', type: 'int', default: 0, comment: '添加时间' })
remark: string; create_time: number;
@Column({ name: 'rules', type: 'json', nullable: true, comment: '权限规则' }) @UpdateDateColumn({ name: 'update_time', type: 'int', default: 0, comment: '最后修改时间' })
rules: string[]; update_time: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' }) @Column({ name: 'is_del', type: 'tinyint', default: 0, comment: '是否删除' })
createTime: Date; is_del: number;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' }) @DeleteDateColumn({ name: 'delete_time', type: 'int', default: 0, comment: '删除时间' })
updateTime: Date; delete_time: number;
// 虚拟字段 // 虚拟字段
statusName?: string; statusName?: string;

View File

@@ -4,63 +4,52 @@
Column, Column,
CreateDateColumn, CreateDateColumn,
UpdateDateColumn, UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm'; } from 'typeorm';
import { BaseEntity } from '../../../core/base/BaseEntity';
/** /**
* 定时任务实体 * 定时任务实体
*/ */
@Entity('sys_schedule') @Entity('sys_schedule')
export class SysSchedule extends BaseEntity { export class SysSchedule {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; 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; key: string;
@Column({ name: 'title', length: 100, comment: '任务标题' }) @Column({ name: 'status', type: 'int', default: 1, 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启用',
})
status: number; status: number;
@CreateDateColumn({ name: 'create_time', comment: '创建时间' }) @Column({ name: 'time', type: 'varchar', length: 500, default: '', comment: '任务周期 json结构' })
createTime: Date; time: string;
@UpdateDateColumn({ name: 'update_time', comment: '更新时间' }) @Column({ name: 'count', type: 'int', default: 0, comment: '执行次数' })
updateTime: Date; 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; statusName?: string;

View File

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

View File

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

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