feat(user): Implement user module foundation
This commit is contained in:
BIN
Binary file not shown.
Generated
+3
-2
@@ -5,9 +5,10 @@
|
|||||||
<option name="apiProjectIds">
|
<option name="apiProjectIds">
|
||||||
<array>
|
<array>
|
||||||
<option value="<byte-array>rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAF0AAp2aWRlby1hdXRocHBwcHBwcHBwcHBwdAAAdAAKdmlkZW8tYXV0aHQACzUuMzQ1NTAwMUU3dAAJNjEyNjYzNC4wdAAGSEEtT1ZQdAAJ5qC555uu5b2VdAABMA==</byte-array>" />
|
<option value="<byte-array>rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAF0AAp2aWRlby1hdXRocHBwcHBwcHBwcHBwdAAAdAAKdmlkZW8tYXV0aHQACzUuMzQ1NTAwMUU3dAAJNjEyNjYzNC4wdAAGSEEtT1ZQdAAJ5qC555uu5b2VdAABMA==</byte-array>" />
|
||||||
|
<option value="<byte-array>rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAF0AAp2aWRlby11c2VycHBwcHBwcHBwcHBwdAAAdAAKdmlkZW8tdXNlcnQACzUuMzY1Mjc1M0U3dAAJNjEyNjYzNC4wdAAGSEEtT1ZQdAAJ5qC555uu5b2VdAABMA==</byte-array>" />
|
||||||
</array>
|
</array>
|
||||||
</option>
|
</option>
|
||||||
<option name="treeNodes" value="<byte-array>rO0ABXNyABdqYXZhLnV0aWwuTGlua2VkSGFzaE1hcDTATlwQbMD7AgABWgALYWNjZXNzT3JkZXJ4cgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAMdwgAAAAQAAAAAXQABzI2NTY1MjFzcgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5UcmVlTm9kZQAAAAAAAAABAgALTAAHYWxsUGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACGNoaWxkcmVudAAPTGphdmEvdXRpbC9NYXA7TAAIZnVsbFBhdGhxAH4ABUwAA2tleXEAfgAFTAAEbmFtZXEAfgAFTAAIcGFyZW50SWRxAH4ABUwACXByb2plY3RJZHEAfgAFTAALcHJvamVjdE5hbWVxAH4ABUwABnRlYW1JZHEAfgAFTAAIdGVhbU5hbWVxAH4ABUwABHR5cGV0ADBMY29tL2l0YW5nY2VudC9pZGVhL3BsdWdpbi9hcGkvYWNjb3VudC9Ob2RlVHlwZTt4cHQADOS4quS6uuWboumYn3NxAH4AAD9AAAAAAAAMdwgAAAAQAAAABHQABzQ2NTczMjZzcQB+AAR0ABnkuKrkurrlm6LpmJ8v5Liq5Lq66aG555uuc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeABwcQB+AAt0ABbkuKrkurrpobnnm64gKDQ2NTczMjYpdAAHMjY1NjUyMXQABzQ2NTczMjZ0AAzkuKrkurrpobnnm650AAcyNjU2NTIxcH5yAC5jb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50Lk5vZGVUeXBlAAAAAAAAAAASAAB4cgAOamF2YS5sYW5nLkVudW0AAAAAAAAAABIAAHhwdAAHUFJPSkVDVHQABzQ2ODIxMTBzcQB+AAR0ABbkuKrkurrlm6LpmJ8veXF4cy1zZWxmc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeABwcQB+ABh0ABN5cXhzLXNlbGYgKDQ2ODIxMTApdAAHMjY1NjUyMXQABzQ2ODIxMTB0AAl5cXhzLXNlbGZ0AAcyNjU2NTIxcHEAfgAWdAAHNDc1NzQ0NHNxAH4ABHQAFuS4quS6uuWboumYny95cXhzLXN5bmNzcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4AHBxAH4AIXQAE3lxeHMtc3luYyAoNDc1NzQ0NCl0AAcyNjU2NTIxdAAHNDc1NzQ0NHQACXlxeHMtc3luY3QABzI2NTY1MjFwcQB+ABZ0AAc2MTI2NjM0c3EAfgAEdAAT5Liq5Lq65Zui6ZifL0hBLU9WUHNxAH4AAD9AAAAAAAAMdwgAAAAQAAAAAXQACzUuMzQ1NTAwMUU3c3EAfgAEdAAe5Liq5Lq65Zui6ZifL0hBLU9WUC92aWRlby1hdXRoc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeAB0AAp2aWRlby1hdXRocQB+AC50AAp2aWRlby1hdXRodAAHNjEyNjYzNHQACTYxMjY2MzQuMHQABkhBLU9WUHBwfnEAfgAUdAAGRk9MREVSeABwcQB+ACp0ABBIQS1PVlAgKDYxMjY2MzQpdAAHMjY1NjUyMXQABzYxMjY2MzR0AAZIQS1PVlB0AAcyNjU2NTIxcHEAfgAWeABwcQB+AAN0AAzkuKrkurrlm6LpmJ9wcHB0AAcyNjU2NTIxdAAM5Liq5Lq65Zui6ZiffnEAfgAUdAAEVEVBTXgA</byte-array>" />
|
<option name="treeNodes" value="<byte-array>rO0ABXNyABdqYXZhLnV0aWwuTGlua2VkSGFzaE1hcDTATlwQbMD7AgABWgALYWNjZXNzT3JkZXJ4cgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAMdwgAAAAQAAAAAXQABzI2NTY1MjFzcgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5UcmVlTm9kZQAAAAAAAAABAgALTAAHYWxsUGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACGNoaWxkcmVudAAPTGphdmEvdXRpbC9NYXA7TAAIZnVsbFBhdGhxAH4ABUwAA2tleXEAfgAFTAAEbmFtZXEAfgAFTAAIcGFyZW50SWRxAH4ABUwACXByb2plY3RJZHEAfgAFTAALcHJvamVjdE5hbWVxAH4ABUwABnRlYW1JZHEAfgAFTAAIdGVhbU5hbWVxAH4ABUwABHR5cGV0ADBMY29tL2l0YW5nY2VudC9pZGVhL3BsdWdpbi9hcGkvYWNjb3VudC9Ob2RlVHlwZTt4cHQADOS4quS6uuWboumYn3NxAH4AAD9AAAAAAAAMdwgAAAAQAAAABHQABzQ2NTczMjZzcQB+AAR0ABnkuKrkurrlm6LpmJ8v5Liq5Lq66aG555uuc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeABwcQB+AAt0ABbkuKrkurrpobnnm64gKDQ2NTczMjYpdAAHMjY1NjUyMXQABzQ2NTczMjZ0AAzkuKrkurrpobnnm650AAcyNjU2NTIxcH5yAC5jb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50Lk5vZGVUeXBlAAAAAAAAAAASAAB4cgAOamF2YS5sYW5nLkVudW0AAAAAAAAAABIAAHhwdAAHUFJPSkVDVHQABzQ2ODIxMTBzcQB+AAR0ABbkuKrkurrlm6LpmJ8veXF4cy1zZWxmc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeABwcQB+ABh0ABN5cXhzLXNlbGYgKDQ2ODIxMTApdAAHMjY1NjUyMXQABzQ2ODIxMTB0AAl5cXhzLXNlbGZ0AAcyNjU2NTIxcHEAfgAWdAAHNDc1NzQ0NHNxAH4ABHQAFuS4quS6uuWboumYny95cXhzLXN5bmNzcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4AHBxAH4AIXQAE3lxeHMtc3luYyAoNDc1NzQ0NCl0AAcyNjU2NTIxdAAHNDc1NzQ0NHQACXlxeHMtc3luY3QABzI2NTY1MjFwcQB+ABZ0AAc2MTI2NjM0c3EAfgAEdAAT5Liq5Lq65Zui6ZifL0hBLU9WUHNxAH4AAD9AAAAAAAAMdwgAAAAQAAAAAnQACzUuMzQ1NTAwMUU3c3EAfgAEdAAe5Liq5Lq65Zui6ZifL0hBLU9WUC92aWRlby1hdXRoc3EAfgAAP0AAAAAAAAx3CAAAABAAAAABdAALNS4zNDU1MDA1RTdzcQB+AAR0AC7kuKrkurrlm6LpmJ8vSEEtT1ZQL3ZpZGVvLWF1dGgv6K6k6K+B5o6n5Yi25Zmoc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeAB0ABp2aWRlby1hdXRoL+iupOivgeaOp+WItuWZqHEAfgAydAAP6K6k6K+B5o6n5Yi25ZmodAALNS4zNDU1MDAxRTd0AAk2MTI2NjM0LjB0AAZIQS1PVlBwcH5xAH4AFHQABkZPTERFUngAdAAKdmlkZW8tYXV0aHEAfgAudAAKdmlkZW8tYXV0aHQABzYxMjY2MzR0AAk2MTI2NjM0LjB0AAZIQS1PVlBwcHEAfgA7dAALNS4zNjUyNzUzRTdzcQB+AAR0AB7kuKrkurrlm6LpmJ8vSEEtT1ZQL3ZpZGVvLXVzZXJzcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4AHQACnZpZGVvLXVzZXJxAH4AQnQACnZpZGVvLXVzZXJ0AAc2MTI2NjM0dAAJNjEyNjYzNC4wdAAGSEEtT1ZQcHBxAH4AO3gAcHEAfgAqdAAQSEEtT1ZQICg2MTI2NjM0KXQABzI2NTY1MjF0AAc2MTI2NjM0dAAGSEEtT1ZQdAAHMjY1NjUyMXBxAH4AFngAcHEAfgADdAAM5Liq5Lq65Zui6ZifcHBwdAAHMjY1NjUyMXQADOS4quS6uuWboumYn35xAH4AFHQABFRFQU14AA==</byte-array>" />
|
||||||
<option name="treeNodesJTree" value="<byte-array>rO0ABXNyACFqYXZheC5zd2luZy50cmVlLkRlZmF1bHRUcmVlTW9kZWynvpEmGsXl2QMAA1oAEmFza3NBbGxvd3NDaGlsZHJlbkwADGxpc3RlbmVyTGlzdHQAJUxqYXZheC9zd2luZy9ldmVudC9FdmVudExpc3RlbmVyTGlzdDtMAARyb290dAAbTGphdmF4L3N3aW5nL3RyZWUvVHJlZU5vZGU7eHAAc3IAI2phdmF4LnN3aW5nLmV2ZW50LkV2ZW50TGlzdGVuZXJMaXN0kUjMLXPfDt4DAAB4cHB4c3IAJ2phdmF4LnN3aW5nLnRyZWUuRGVmYXVsdE11dGFibGVUcmVlTm9kZcRYv/zyqHHgAwADWgAOYWxsb3dzQ2hpbGRyZW5MAAhjaGlsZHJlbnQAEkxqYXZhL3V0aWwvVmVjdG9yO0wABnBhcmVudHQAIkxqYXZheC9zd2luZy90cmVlL011dGFibGVUcmVlTm9kZTt4cAFzcgAQamF2YS51dGlsLlZlY3RvctmXfVuAO68BAwADSQARY2FwYWNpdHlJbmNyZW1lbnRJAAxlbGVtZW50Q291bnRbAAtlbGVtZW50RGF0YXQAE1tMamF2YS9sYW5nL09iamVjdDt4cAAAAAAAAAABdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAACnNxAH4ABgFzcQB+AAoAAAAAAAAABHVxAH4ADQAAAApzcQB+AAYBcHEAfgAPdXEAfgANAAAAAnQACnVzZXJPYmplY3RzcgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5UcmVlTm9kZQAAAAAAAAABAgALTAAHYWxsUGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACGNoaWxkcmVudAAPTGphdmEvdXRpbC9NYXA7TAAIZnVsbFBhdGhxAH4AFkwAA2tleXEAfgAWTAAEbmFtZXEAfgAWTAAIcGFyZW50SWRxAH4AFkwACXByb2plY3RJZHEAfgAWTAALcHJvamVjdE5hbWVxAH4AFkwABnRlYW1JZHEAfgAWTAAIdGVhbU5hbWVxAH4AFkwABHR5cGV0ADBMY29tL2l0YW5nY2VudC9pZGVhL3BsdWdpbi9hcGkvYWNjb3VudC9Ob2RlVHlwZTt4cHQAGeS4quS6uuWboumYny/kuKrkurrpobnnm65zcgAXamF2YS51dGlsLkxpbmtlZEhhc2hNYXA0wE5cEGzA+wIAAVoAC2FjY2Vzc09yZGVyeHIAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAc0NjU3MzI2dAAW5Liq5Lq66aG555uuICg0NjU3MzI2KXQABzI2NTY1MjF0AAc0NjU3MzI2dAAM5Liq5Lq66aG555uudAAHMjY1NjUyMXB+cgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5Ob2RlVHlwZQAAAAAAAAAAEgAAeHIADmphdmEubGFuZy5FbnVtAAAAAAAAAAASAAB4cHQAB1BST0pFQ1R4c3EAfgAGAXBxAH4AD3VxAH4ADQAAAAJxAH4AFHNxAH4AFXQAFuS4quS6uuWboumYny95cXhzLXNlbGZzcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAc0NjgyMTEwdAATeXF4cy1zZWxmICg0NjgyMTEwKXQABzI2NTY1MjF0AAc0NjgyMTEwdAAJeXF4cy1zZWxmdAAHMjY1NjUyMXBxAH4AJnhzcQB+AAYBcHEAfgAPdXEAfgANAAAAAnEAfgAUc3EAfgAVdAAW5Liq5Lq65Zui6ZifL3lxeHMtc3luY3NxAH4AGz9AAAAAAAAAdwgAAAAQAAAAAHgAcHQABzQ3NTc0NDR0ABN5cXhzLXN5bmMgKDQ3NTc0NDQpdAAHMjY1NjUyMXQABzQ3NTc0NDR0AAl5cXhzLXN5bmN0AAcyNjU2NTIxcHEAfgAmeHNxAH4ABgFzcQB+AAoAAAAAAAAAAXVxAH4ADQAAAApzcQB+AAYBcHEAfgA+dXEAfgANAAAAAnEAfgAUc3EAfgAVdAAe5Liq5Lq65Zui6ZifL0hBLU9WUC92aWRlby1hdXRoc3EAfgAbP0AAAAAAAAB3CAAAABAAAAAAeAB0AAp2aWRlby1hdXRodAALNS4zNDU1MDAxRTd0AAp2aWRlby1hdXRodAAHNjEyNjYzNHQACTYxMjY2MzQuMHQABkhBLU9WUHBwfnEAfgAkdAAGRk9MREVSeHBwcHBwcHBwcHhxAH4AD3VxAH4ADQAAAAJxAH4AFHNxAH4AFXQAE+S4quS6uuWboumYny9IQS1PVlBzcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAc2MTI2NjM0dAAQSEEtT1ZQICg2MTI2NjM0KXQABzI2NTY1MjF0AAc2MTI2NjM0dAAGSEEtT1ZQdAAHMjY1NjUyMXBxAH4AJnhwcHBwcHB4cQB+AAl1cQB+AA0AAAACcQB+ABRzcQB+ABV0AAzkuKrkurrlm6LpmJ9zcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAcyNjU2NTIxdAAM5Liq5Lq65Zui6ZifcHBwdAAHMjY1NjUyMXQADOS4quS6uuWboumYn35xAH4AJHQABFRFQU14cHBwcHBwcHBweHB1cQB+AA0AAAACcQB+ABRzcQB+ABV0AARSb290cHB0AAEwcQB+AGRwcHBwcHEAfgBgeHNxAH4ACgAAAAAAAAACdXEAfgANAAAACnQABHJvb3RxAH4ACXBwcHBwcHBweHg=</byte-array>" />
|
<option name="treeNodesJTree" value="<byte-array>rO0ABXNyACFqYXZheC5zd2luZy50cmVlLkRlZmF1bHRUcmVlTW9kZWynvpEmGsXl2QMAA1oAEmFza3NBbGxvd3NDaGlsZHJlbkwADGxpc3RlbmVyTGlzdHQAJUxqYXZheC9zd2luZy9ldmVudC9FdmVudExpc3RlbmVyTGlzdDtMAARyb290dAAbTGphdmF4L3N3aW5nL3RyZWUvVHJlZU5vZGU7eHAAc3IAI2phdmF4LnN3aW5nLmV2ZW50LkV2ZW50TGlzdGVuZXJMaXN0kUjMLXPfDt4DAAB4cHB4c3IAJ2phdmF4LnN3aW5nLnRyZWUuRGVmYXVsdE11dGFibGVUcmVlTm9kZcRYv/zyqHHgAwADWgAOYWxsb3dzQ2hpbGRyZW5MAAhjaGlsZHJlbnQAEkxqYXZhL3V0aWwvVmVjdG9yO0wABnBhcmVudHQAIkxqYXZheC9zd2luZy90cmVlL011dGFibGVUcmVlTm9kZTt4cAFzcgAQamF2YS51dGlsLlZlY3RvctmXfVuAO68BAwADSQARY2FwYWNpdHlJbmNyZW1lbnRJAAxlbGVtZW50Q291bnRbAAtlbGVtZW50RGF0YXQAE1tMamF2YS9sYW5nL09iamVjdDt4cAAAAAAAAAABdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAACnNxAH4ABgFzcQB+AAoAAAAAAAAABHVxAH4ADQAAAApzcQB+AAYBcHEAfgAPdXEAfgANAAAAAnQACnVzZXJPYmplY3RzcgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5UcmVlTm9kZQAAAAAAAAABAgALTAAHYWxsUGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACGNoaWxkcmVudAAPTGphdmEvdXRpbC9NYXA7TAAIZnVsbFBhdGhxAH4AFkwAA2tleXEAfgAWTAAEbmFtZXEAfgAWTAAIcGFyZW50SWRxAH4AFkwACXByb2plY3RJZHEAfgAWTAALcHJvamVjdE5hbWVxAH4AFkwABnRlYW1JZHEAfgAWTAAIdGVhbU5hbWVxAH4AFkwABHR5cGV0ADBMY29tL2l0YW5nY2VudC9pZGVhL3BsdWdpbi9hcGkvYWNjb3VudC9Ob2RlVHlwZTt4cHQAGeS4quS6uuWboumYny/kuKrkurrpobnnm65zcgAXamF2YS51dGlsLkxpbmtlZEhhc2hNYXA0wE5cEGzA+wIAAVoAC2FjY2Vzc09yZGVyeHIAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAc0NjU3MzI2dAAW5Liq5Lq66aG555uuICg0NjU3MzI2KXQABzI2NTY1MjF0AAc0NjU3MzI2dAAM5Liq5Lq66aG555uudAAHMjY1NjUyMXB+cgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5Ob2RlVHlwZQAAAAAAAAAAEgAAeHIADmphdmEubGFuZy5FbnVtAAAAAAAAAAASAAB4cHQAB1BST0pFQ1R4c3EAfgAGAXBxAH4AD3VxAH4ADQAAAAJxAH4AFHNxAH4AFXQAFuS4quS6uuWboumYny95cXhzLXNlbGZzcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAc0NjgyMTEwdAATeXF4cy1zZWxmICg0NjgyMTEwKXQABzI2NTY1MjF0AAc0NjgyMTEwdAAJeXF4cy1zZWxmdAAHMjY1NjUyMXBxAH4AJnhzcQB+AAYBcHEAfgAPdXEAfgANAAAAAnEAfgAUc3EAfgAVdAAW5Liq5Lq65Zui6ZifL3lxeHMtc3luY3NxAH4AGz9AAAAAAAAAdwgAAAAQAAAAAHgAcHQABzQ3NTc0NDR0ABN5cXhzLXN5bmMgKDQ3NTc0NDQpdAAHMjY1NjUyMXQABzQ3NTc0NDR0AAl5cXhzLXN5bmN0AAcyNjU2NTIxcHEAfgAmeHNxAH4ABgFzcQB+AAoAAAAAAAAAAnVxAH4ADQAAAApzcQB+AAYBc3EAfgAKAAAAAAAAAAF1cQB+AA0AAAAKc3EAfgAGAXBxAH4AQXVxAH4ADQAAAAJxAH4AFHNxAH4AFXQALuS4quS6uuWboumYny9IQS1PVlAvdmlkZW8tYXV0aC/orqTor4HmjqfliLblmahzcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHQAGnZpZGVvLWF1dGgv6K6k6K+B5o6n5Yi25ZmodAALNS4zNDU1MDA1RTd0AA/orqTor4HmjqfliLblmah0AAs1LjM0NTUwMDFFN3QACTYxMjY2MzQuMHQABkhBLU9WUHBwfnEAfgAkdAAGRk9MREVSeHBwcHBwcHBwcHhxAH4APnVxAH4ADQAAAAJxAH4AFHNxAH4AFXQAHuS4quS6uuWboumYny9IQS1PVlAvdmlkZW8tYXV0aHNxAH4AGz9AAAAAAAAAdwgAAAAQAAAAAHgAdAAKdmlkZW8tYXV0aHQACzUuMzQ1NTAwMUU3dAAKdmlkZW8tYXV0aHQABzYxMjY2MzR0AAk2MTI2NjM0LjB0AAZIQS1PVlBwcHEAfgBPeHNxAH4ABgFwcQB+AD51cQB+AA0AAAACcQB+ABRzcQB+ABV0AB7kuKrkurrlm6LpmJ8vSEEtT1ZQL3ZpZGVvLXVzZXJzcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHQACnZpZGVvLXVzZXJ0AAs1LjM2NTI3NTNFN3QACnZpZGVvLXVzZXJ0AAc2MTI2NjM0dAAJNjEyNjYzNC4wdAAGSEEtT1ZQcHBxAH4AT3hwcHBwcHBwcHhxAH4AD3VxAH4ADQAAAAJxAH4AFHNxAH4AFXQAE+S4quS6uuWboumYny9IQS1PVlBzcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAc2MTI2NjM0dAAQSEEtT1ZQICg2MTI2NjM0KXQABzI2NTY1MjF0AAc2MTI2NjM0dAAGSEEtT1ZQdAAHMjY1NjUyMXBxAH4AJnhwcHBwcHB4cQB+AAl1cQB+AA0AAAACcQB+ABRzcQB+ABV0AAzkuKrkurrlm6LpmJ9zcQB+ABs/QAAAAAAAAHcIAAAAEAAAAAB4AHB0AAcyNjU2NTIxdAAM5Liq5Lq65Zui6ZifcHBwdAAHMjY1NjUyMXQADOS4quS6uuWboumYn35xAH4AJHQABFRFQU14cHBwcHBwcHBweHB1cQB+AA0AAAACcQB+ABRzcQB+ABV0AARSb290cHB0AAEwcQB+AHxwcHBwcHEAfgB4eHNxAH4ACgAAAAAAAAACdXEAfgANAAAACnQABHJvb3RxAH4ACXBwcHBwcHBweHg=</byte-array>" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,751 @@
|
|||||||
|
# video-user模块目录架构、功能及接口文档
|
||||||
|
|
||||||
|
## 一、目录架构
|
||||||
|
|
||||||
|
```
|
||||||
|
video-user/
|
||||||
|
├── src/
|
||||||
|
│ ├── main/
|
||||||
|
│ │ ├── java/
|
||||||
|
│ │ │ └── com/nuaa/video/user/
|
||||||
|
│ │ │ ├── config/ # 配置类
|
||||||
|
│ │ │ │ ├── FeignConfig.java # Feign配置类
|
||||||
|
│ │ │ │ ├── MinioConfig.java # Minio配置类
|
||||||
|
│ │ │ │ └── SecurityConfig.java # 安全配置类
|
||||||
|
│ │ │ ├── controller/ # 控制器
|
||||||
|
│ │ │ │ ├── UserProfileController.java # 用户资料控制器
|
||||||
|
│ │ │ │ ├── UserFollowController.java # 用户关注控制器
|
||||||
|
│ │ │ │ ├── UserMessageController.java # 用户私信控制器
|
||||||
|
│ │ │ │ ├── UserNotificationController.java # 用户通知控制器
|
||||||
|
│ │ │ │ └── UserSettingController.java # 用户设置控制器
|
||||||
|
│ │ │ ├── dto/ # 数据传输对象
|
||||||
|
│ │ │ │ ├── UserProfileDTO.java # 用户资料DTO
|
||||||
|
│ │ │ │ ├── UserMessageDTO.java # 用户私信DTO
|
||||||
|
│ │ │ │ └── UserSettingDTO.java # 用户设置DTO
|
||||||
|
│ │ │ ├── entity/ # 实体类
|
||||||
|
│ │ │ │ ├── UserInfo.java # 用户信息实体
|
||||||
|
│ │ │ │ ├── UserFollow.java # 用户关注实体
|
||||||
|
│ │ │ │ ├── UserStatDaily.java # 用户每日统计实体
|
||||||
|
│ │ │ │ ├── UserMessage.java # 用户私信实体
|
||||||
|
│ │ │ │ ├── UserNotification.java # 用户通知实体
|
||||||
|
│ │ │ │ └── UserSetting.java # 用户设置实体
|
||||||
|
│ │ │ ├── feign/ # Feign客户端
|
||||||
|
│ │ │ │ └── AuthFeignClient.java # 认证服务Feign客户端
|
||||||
|
│ │ │ ├── filter/ # 过滤器
|
||||||
|
│ │ │ │ └── JwtAuthenticationFilter.java # JWT认证过滤器
|
||||||
|
│ │ │ ├── mapper/ # MyBatis映射接口
|
||||||
|
│ │ │ │ ├── UserInfoMapper.java # 用户信息Mapper
|
||||||
|
│ │ │ │ ├── UserFollowMapper.java # 用户关注Mapper
|
||||||
|
│ │ │ │ ├── UserStatDailyMapper.java # 用户每日统计Mapper
|
||||||
|
│ │ │ │ ├── UserMessageMapper.java # 用户私信Mapper
|
||||||
|
│ │ │ │ ├── UserNotificationMapper.java # 用户通知Mapper
|
||||||
|
│ │ │ │ └── UserSettingMapper.java # 用户设置Mapper
|
||||||
|
│ │ │ ├── service/ # 服务层
|
||||||
|
│ │ │ │ ├── UserInfoService.java # 用户信息服务接口
|
||||||
|
│ │ │ │ ├── UserFollowService.java # 用户关注服务接口
|
||||||
|
│ │ │ │ ├── UserMessageService.java # 用户私信服务接口
|
||||||
|
│ │ │ │ ├── UserNotificationService.java # 用户通知服务接口
|
||||||
|
│ │ │ │ ├── UserSettingService.java # 用户设置服务接口
|
||||||
|
│ │ │ │ └── impl/ # 实现类
|
||||||
|
│ │ │ │ ├── UserInfoServiceImpl.java
|
||||||
|
│ │ │ │ ├── UserFollowServiceImpl.java
|
||||||
|
│ │ │ │ ├── UserMessageServiceImpl.java
|
||||||
|
│ │ │ │ ├── UserNotificationServiceImpl.java
|
||||||
|
│ │ │ │ └── UserSettingServiceImpl.java
|
||||||
|
│ │ │ ├── vo/ # 视图对象
|
||||||
|
│ │ │ │ ├── UserProfileVO.java # 用户资料VO
|
||||||
|
│ │ │ │ ├── UserMessageVO.java # 用户私信VO
|
||||||
|
│ │ │ │ └── UserNotificationVO.java # 用户通知VO
|
||||||
|
│ │ │ └── UserApplication.java # 启动类
|
||||||
|
│ │ └── resources/
|
||||||
|
│ │ ├── mapper/ # MyBatis XML映射文件
|
||||||
|
│ │ │ ├── UserInfoMapper.xml
|
||||||
|
│ │ │ ├── UserFollowMapper.xml
|
||||||
|
│ │ │ ├── UserMessageMapper.xml
|
||||||
|
│ │ │ └── UserNotificationMapper.xml
|
||||||
|
│ │ ├── application.yml # 主配置文件
|
||||||
|
│ │ └── application-dev.yml # 开发环境配置
|
||||||
|
└── pom.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 二、功能概述
|
||||||
|
|
||||||
|
### 1. 核心功能
|
||||||
|
|
||||||
|
- **用户资料管理**:创建、查询、更新用户资料,上传头像
|
||||||
|
- **用户关系管理**:关注/取消关注用户,查看关注列表和粉丝列表
|
||||||
|
- **私信系统**:发送和接收私信,管理会话和消息状态
|
||||||
|
- **通知系统**:管理用户收到的系统通知和用户互动通知
|
||||||
|
- **用户设置**:管理用户隐私和通知偏好设置
|
||||||
|
- **用户数据统计**:收集和查询用户活动统计信息
|
||||||
|
|
||||||
|
### 2. 业务流程
|
||||||
|
|
||||||
|
1. **用户资料流程**:
|
||||||
|
- 用户注册时,auth服务创建基本认证信息
|
||||||
|
- auth服务通过Feign调用user服务创建用户资料
|
||||||
|
- 用户可随时更新个人资料信息
|
||||||
|
|
||||||
|
2. **用户关系流程**:
|
||||||
|
- 用户关注他人,增加自己的关注数和对方的粉丝数
|
||||||
|
- 系统为被关注用户创建通知
|
||||||
|
- 用户可查看关注列表和粉丝列表
|
||||||
|
|
||||||
|
3. **私信流程**:
|
||||||
|
- 用户发送私信给其他用户
|
||||||
|
- 接收用户收到未读消息通知
|
||||||
|
- 用户可查看会话列表和详细私信记录
|
||||||
|
|
||||||
|
4. **通知流程**:
|
||||||
|
- 系统根据用户互动(点赞、评论、关注)创建通知
|
||||||
|
- 用户可查看不同类型的通知
|
||||||
|
- 用户可标记通知为已读
|
||||||
|
|
||||||
|
## 三、接口文档
|
||||||
|
|
||||||
|
### 1. 用户资料接口
|
||||||
|
|
||||||
|
#### 1.1 初始化用户资料
|
||||||
|
|
||||||
|
- **URI**: `/user/profile/init`
|
||||||
|
- **方法**: POST
|
||||||
|
- **描述**: 服务内部接口,初始化用户资料(由auth服务在用户注册时调用)
|
||||||
|
- **请求参数**:
|
||||||
|
- `userId`: 用户ID
|
||||||
|
- `username`: 用户名
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **错误码**:
|
||||||
|
- `400`: 参数错误
|
||||||
|
- `500`: 服务器内部错误
|
||||||
|
|
||||||
|
#### 1.2 获取当前用户资料
|
||||||
|
|
||||||
|
- **URI**: `/user/profile/current`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取当前登录用户的资料
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"nickname": "用户昵称",
|
||||||
|
"avatar": "头像URL",
|
||||||
|
"gender": 1,
|
||||||
|
"birthday": "1990-01-01",
|
||||||
|
"signature": "个性签名",
|
||||||
|
"region": "地区",
|
||||||
|
"followerCount": 10,
|
||||||
|
"followingCount": 20,
|
||||||
|
"videoCount": 5,
|
||||||
|
"likeCount": 100,
|
||||||
|
"level": 3,
|
||||||
|
"vipLevel": 0,
|
||||||
|
"vipExpireTime": null,
|
||||||
|
"isFollowed": false,
|
||||||
|
"createTime": "2023-01-01T12:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 获取指定用户资料
|
||||||
|
|
||||||
|
- **URI**: `/user/profile/public/{userId}`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取指定用户的资料
|
||||||
|
- **路径参数**:
|
||||||
|
- `userId`: 用户ID
|
||||||
|
- **响应**: 与获取当前用户资料相同
|
||||||
|
|
||||||
|
#### 1.4 更新用户资料
|
||||||
|
|
||||||
|
- **URI**: `/user/profile`
|
||||||
|
- **方法**: PUT
|
||||||
|
- **描述**: 更新当前用户资料
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **请求Body**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nickname": "新昵称",
|
||||||
|
"gender": 1,
|
||||||
|
"birthday": "1990-01-01",
|
||||||
|
"signature": "新的个性签名",
|
||||||
|
"region": "新的地区"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.5 上传头像
|
||||||
|
|
||||||
|
- **URI**: `/user/profile/avatar`
|
||||||
|
- **方法**: POST
|
||||||
|
- **描述**: 上传用户头像
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- `Content-Type`: multipart/form-data
|
||||||
|
- **请求参数**:
|
||||||
|
- `file`: 图片文件
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": "头像URL"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.6 搜索用户
|
||||||
|
|
||||||
|
- **URI**: `/user/profile/search`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 按关键词搜索用户
|
||||||
|
- **请求参数**:
|
||||||
|
- `keyword`: 搜索关键词
|
||||||
|
- `page`: 页码(默认1)
|
||||||
|
- `size`: 每页数量(默认10)
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"nickname": "用户昵称",
|
||||||
|
"avatar": "头像URL",
|
||||||
|
"signature": "个性签名",
|
||||||
|
"followerCount": 10,
|
||||||
|
"followingCount": 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 100,
|
||||||
|
"size": 10,
|
||||||
|
"current": 1,
|
||||||
|
"pages": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 用户关注接口
|
||||||
|
|
||||||
|
#### 2.1 关注用户
|
||||||
|
|
||||||
|
- **URI**: `/user/follow/{followId}`
|
||||||
|
- **方法**: POST
|
||||||
|
- **描述**: 关注指定用户
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **路径参数**:
|
||||||
|
- `followId`: 要关注的用户ID
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **错误码**:
|
||||||
|
- `400`: 参数错误(如已关注)
|
||||||
|
- `404`: 用户不存在
|
||||||
|
|
||||||
|
#### 2.2 取消关注
|
||||||
|
|
||||||
|
- **URI**: `/user/follow/{followId}`
|
||||||
|
- **方法**: DELETE
|
||||||
|
- **描述**: 取消关注指定用户
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **路径参数**:
|
||||||
|
- `followId`: 要取消关注的用户ID
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 获取关注列表
|
||||||
|
|
||||||
|
- **URI**: `/user/follow/following/{userId}`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取指定用户的关注列表
|
||||||
|
- **路径参数**:
|
||||||
|
- `userId`: 用户ID
|
||||||
|
- **请求参数**:
|
||||||
|
- `page`: 页码(默认1)
|
||||||
|
- `size`: 每页数量(默认10)
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"nickname": "被关注用户",
|
||||||
|
"avatar": "头像URL",
|
||||||
|
"signature": "个性签名",
|
||||||
|
"followerCount": 100,
|
||||||
|
"followingCount": 50,
|
||||||
|
"isFollowed": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 20,
|
||||||
|
"size": 10,
|
||||||
|
"current": 1,
|
||||||
|
"pages": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 获取粉丝列表
|
||||||
|
|
||||||
|
- **URI**: `/user/follow/followers/{userId}`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取指定用户的粉丝列表
|
||||||
|
- **路径参数**:
|
||||||
|
- `userId`: 用户ID
|
||||||
|
- **请求参数**:
|
||||||
|
- `page`: 页码(默认1)
|
||||||
|
- `size`: 每页数量(默认10)
|
||||||
|
- **响应**: 与关注列表格式相同
|
||||||
|
|
||||||
|
#### 2.5 检查是否已关注
|
||||||
|
|
||||||
|
- **URI**: `/user/follow/check/{followId}`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 检查当前用户是否已关注指定用户
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **路径参数**:
|
||||||
|
- `followId`: 被检查的用户ID
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": true // 或 false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 用户私信接口
|
||||||
|
|
||||||
|
#### 3.1 发送私信
|
||||||
|
|
||||||
|
- **URI**: `/user/message`
|
||||||
|
- **方法**: POST
|
||||||
|
- **描述**: 发送私信给指定用户
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **请求Body**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"receiverId": 2,
|
||||||
|
"content": "私信内容"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 获取私信会话列表
|
||||||
|
|
||||||
|
- **URI**: `/user/message/list`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取当前用户的私信会话列表
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **请求参数**:
|
||||||
|
- `page`: 页码(默认1)
|
||||||
|
- `size`: 每页数量(默认10)
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 100,
|
||||||
|
"senderId": 2,
|
||||||
|
"senderNickname": "发送者昵称",
|
||||||
|
"senderAvatar": "发送者头像URL",
|
||||||
|
"receiverId": 1,
|
||||||
|
"receiverNickname": "接收者昵称",
|
||||||
|
"receiverAvatar": "接收者头像URL",
|
||||||
|
"content": "最新消息内容",
|
||||||
|
"isRead": 0,
|
||||||
|
"createTime": "2023-05-20T14:30:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 5,
|
||||||
|
"size": 10,
|
||||||
|
"current": 1,
|
||||||
|
"pages": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3 获取私信详情
|
||||||
|
|
||||||
|
- **URI**: `/user/message/detail/{targetId}`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取与指定用户的私信记录
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **路径参数**:
|
||||||
|
- `targetId`: 目标用户ID
|
||||||
|
- **请求参数**:
|
||||||
|
- `page`: 页码(默认1)
|
||||||
|
- `size`: 每页数量(默认20)
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 100,
|
||||||
|
"senderId": 1,
|
||||||
|
"senderNickname": "当前用户",
|
||||||
|
"senderAvatar": "头像URL",
|
||||||
|
"receiverId": 2,
|
||||||
|
"receiverNickname": "目标用户",
|
||||||
|
"receiverAvatar": "头像URL",
|
||||||
|
"content": "你好",
|
||||||
|
"isRead": 1,
|
||||||
|
"createTime": "2023-05-20T14:28:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 101,
|
||||||
|
"senderId": 2,
|
||||||
|
"senderNickname": "目标用户",
|
||||||
|
"senderAvatar": "头像URL",
|
||||||
|
"receiverId": 1,
|
||||||
|
"receiverNickname": "当前用户",
|
||||||
|
"receiverAvatar": "头像URL",
|
||||||
|
"content": "你好,很高兴认识你",
|
||||||
|
"isRead": 1,
|
||||||
|
"createTime": "2023-05-20T14:30:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 2,
|
||||||
|
"size": 20,
|
||||||
|
"current": 1,
|
||||||
|
"pages": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.4 标记私信为已读
|
||||||
|
|
||||||
|
- **URI**: `/user/message/read/{senderId}`
|
||||||
|
- **方法**: PUT
|
||||||
|
- **描述**: 标记来自指定发送者的所有私信为已读
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **路径参数**:
|
||||||
|
- `senderId`: 发送者用户ID
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.5 获取未读私信数量
|
||||||
|
|
||||||
|
- **URI**: `/user/message/unread/count`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取当前用户的未读私信数量
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 用户通知接口
|
||||||
|
|
||||||
|
#### 4.1 获取通知列表
|
||||||
|
|
||||||
|
- **URI**: `/user/notification/list`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取用户通知列表
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **请求参数**:
|
||||||
|
- `type`: 通知类型(1:点赞,2:评论,3:关注,4:系统通知,可选)
|
||||||
|
- `page`: 页码(默认1)
|
||||||
|
- `size`: 每页数量(默认10)
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": 3,
|
||||||
|
"content": "用户小明关注了你",
|
||||||
|
"relatedId": 123,
|
||||||
|
"isRead": 0,
|
||||||
|
"createTime": "2023-05-20T10:00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 50,
|
||||||
|
"size": 10,
|
||||||
|
"current": 1,
|
||||||
|
"pages": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 标记所有通知为已读
|
||||||
|
|
||||||
|
- **URI**: `/user/notification/read`
|
||||||
|
- **方法**: PUT
|
||||||
|
- **描述**: 标记当前用户的所有通知为已读
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.3 标记指定通知为已读
|
||||||
|
|
||||||
|
- **URI**: `/user/notification/read/{notificationId}`
|
||||||
|
- **方法**: PUT
|
||||||
|
- **描述**: 标记指定通知为已读
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **路径参数**:
|
||||||
|
- `notificationId`: 通知ID
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.4 获取未读通知数量
|
||||||
|
|
||||||
|
- **URI**: `/user/notification/unread/count`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取当前用户的未读通知数量
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 用户设置接口
|
||||||
|
|
||||||
|
#### 5.1 获取用户设置
|
||||||
|
|
||||||
|
- **URI**: `/user/setting`
|
||||||
|
- **方法**: GET
|
||||||
|
- **描述**: 获取当前用户的设置
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"privacyLevel": 1,
|
||||||
|
"commentNotify": 1,
|
||||||
|
"likeNotify": 1,
|
||||||
|
"followNotify": 1,
|
||||||
|
"systemNotify": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 更新用户设置
|
||||||
|
|
||||||
|
- **URI**: `/user/setting`
|
||||||
|
- **方法**: PUT
|
||||||
|
- **描述**: 更新当前用户的设置
|
||||||
|
- **请求头**:
|
||||||
|
- `Authorization`: Bearer {accessToken}
|
||||||
|
- **请求Body**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"privacyLevel": 2,
|
||||||
|
"commentNotify": 1,
|
||||||
|
"likeNotify": 0,
|
||||||
|
"followNotify": 1,
|
||||||
|
"systemNotify": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、数据库表结构
|
||||||
|
|
||||||
|
### 1. user_info (用户信息表)
|
||||||
|
- `id`: bigint - 用户ID (主键)
|
||||||
|
- `nickname`: varchar(64) - 昵称
|
||||||
|
- `avatar`: varchar(256) - 头像URL
|
||||||
|
- `gender`: tinyint - 性别(0:未知,1:男,2:女)
|
||||||
|
- `birthday`: date - 生日
|
||||||
|
- `signature`: varchar(256) - 个性签名
|
||||||
|
- `region`: varchar(128) - 地区
|
||||||
|
- `follower_count`: int - 粉丝数
|
||||||
|
- `following_count`: int - 关注数
|
||||||
|
- `video_count`: int - 视频数
|
||||||
|
- `like_count`: int - 获赞数
|
||||||
|
- `coin_count`: int - 硬币数
|
||||||
|
- `favorite_count`: int - 收藏数
|
||||||
|
- `view_count`: bigint - 播放数
|
||||||
|
- `level`: tinyint - 等级
|
||||||
|
- `vip_level`: tinyint - VIP等级
|
||||||
|
- `vip_expire_time`: datetime - VIP到期时间
|
||||||
|
- `created_by`: varchar(64) - 创建人
|
||||||
|
- `updated_by`: varchar(64) - 更新人
|
||||||
|
- `create_time`: datetime - 创建时间
|
||||||
|
- `update_time`: datetime - 更新时间
|
||||||
|
|
||||||
|
### 2. user_follow (用户关注表)
|
||||||
|
- `id`: bigint - 主键ID
|
||||||
|
- `user_id`: bigint - 用户ID
|
||||||
|
- `follow_id`: bigint - 关注用户ID
|
||||||
|
- `create_time`: datetime - 创建时间
|
||||||
|
|
||||||
|
### 3. user_stat_daily (用户每日统计表)
|
||||||
|
- `id`: bigint - 主键ID
|
||||||
|
- `user_id`: bigint - 用户ID
|
||||||
|
- `stat_date`: date - 统计日期
|
||||||
|
- `follower_increment`: int - 新增粉丝数
|
||||||
|
- `following_increment`: int - 新增关注数
|
||||||
|
- `video_increment`: int - 新增视频数
|
||||||
|
- `like_increment`: int - 新增获赞数
|
||||||
|
- `coin_increment`: int - 新增硬币数
|
||||||
|
- `favorite_increment`: int - 新增收藏数
|
||||||
|
- `view_increment`: bigint - 新增播放数
|
||||||
|
- `create_time`: datetime - 创建时间
|
||||||
|
|
||||||
|
### 4. user_message (用户私信表)
|
||||||
|
- `id`: bigint - 主键ID
|
||||||
|
- `sender_id`: bigint - 发送者ID
|
||||||
|
- `receiver_id`: bigint - 接收者ID
|
||||||
|
- `content`: text - 消息内容
|
||||||
|
- `is_read`: tinyint - 是否已读(0:未读,1:已读)
|
||||||
|
- `create_time`: datetime - 创建时间
|
||||||
|
|
||||||
|
### 5. user_notification (用户通知表)
|
||||||
|
- `id`: bigint - 主键ID
|
||||||
|
- `user_id`: bigint - 用户ID
|
||||||
|
- `type`: tinyint - 通知类型(1:点赞,2:评论,3:关注,4:系统通知)
|
||||||
|
- `content`: text - 通知内容
|
||||||
|
- `related_id`: bigint - 相关ID
|
||||||
|
- `is_read`: tinyint - 是否已读(0:未读,1:已读)
|
||||||
|
- `create_time`: datetime - 创建时间
|
||||||
|
|
||||||
|
### 6. user_setting (用户设置表)
|
||||||
|
- `id`: bigint - 主键ID
|
||||||
|
- `user_id`: bigint - 用户ID
|
||||||
|
- `privacy_level`: tinyint - 隐私等级(1:公开,2:仅关注,3:私密)
|
||||||
|
- `comment_notify`: tinyint - 评论通知(0:关闭,1:开启)
|
||||||
|
- `like_notify`: tinyint - 点赞通知(0:关闭,1:开启)
|
||||||
|
- `follow_notify`: tinyint - 关注通知(0:关闭,1:开启)
|
||||||
|
- `system_notify`: tinyint - 系统通知(0:关闭,1:开启)
|
||||||
|
- `created_by`: varchar(64) - 创建人
|
||||||
|
- `updated_by`: varchar(64) - 更新人
|
||||||
|
- `create_time`: datetime - 创建时间
|
||||||
|
- `update_time`: datetime - 更新时间
|
||||||
|
|
||||||
|
## 五、调用示例
|
||||||
|
|
||||||
|
### 1. 获取用户资料
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用cURL
|
||||||
|
curl -X GET http://localhost:9002/user/profile/current \
|
||||||
|
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
|
||||||
|
|
||||||
|
# 使用JavaScript
|
||||||
|
fetch('http://localhost:9002/user/profile/current', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiJ9...'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => console.log(data));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 关注用户
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用cURL
|
||||||
|
curl -X POST http://localhost:9002/user/follow/2 \
|
||||||
|
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
|
||||||
|
|
||||||
|
# 使用JavaScript
|
||||||
|
fetch('http://localhost:9002/user/follow/2', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiJ9...'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => console.log(data));
|
||||||
|
```
|
||||||
|
|
||||||
|
video-user模块是整个视频平台的核心组成部分,它提供了完整的用户信息管理、社交互动和消息通知功能,为用户提供良好的社交体验。
|
||||||
@@ -113,6 +113,12 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- LoadBalancer -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -113,4 +113,13 @@ public class AuthController {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取用户名
|
||||||
|
*/
|
||||||
|
@GetMapping("/username/{userId}")
|
||||||
|
public String getUsernameById(@PathVariable Long userId) {
|
||||||
|
AuthUser user = authUserService.getById(userId);
|
||||||
|
return user != null ? user.getUsername() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.nuaa.video.auth.feign;
|
||||||
|
|
||||||
|
import com.nuaa.video.auth.feign.fallback.UserClientFallback;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
@FeignClient(name = "video-user", fallback = UserClientFallback.class)
|
||||||
|
public interface UserClient {
|
||||||
|
|
||||||
|
@PostMapping("/user/profile/init")
|
||||||
|
Result<Void> createUserInfo(@RequestParam("userId") Long userId,
|
||||||
|
@RequestParam("username") String username);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.nuaa.video.auth.feign.fallback;
|
||||||
|
|
||||||
|
import com.nuaa.video.auth.feign.UserClient;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class UserClientFallback implements UserClient {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Void> createUserInfo(Long userId, String username) {
|
||||||
|
log.error("创建用户资料失败(回退): userId={}, username={}", userId, username);
|
||||||
|
return Result.error("用户服务不可用,资料将在首次访问时创建");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import com.nuaa.video.auth.dto.RegisterDTO;
|
|||||||
import com.nuaa.video.auth.dto.TokenInfoDTO;
|
import com.nuaa.video.auth.dto.TokenInfoDTO;
|
||||||
import com.nuaa.video.auth.entity.AuthUser;
|
import com.nuaa.video.auth.entity.AuthUser;
|
||||||
import com.nuaa.video.auth.entity.AuthUserRole;
|
import com.nuaa.video.auth.entity.AuthUserRole;
|
||||||
|
import com.nuaa.video.auth.feign.UserClient;
|
||||||
import com.nuaa.video.auth.mapper.AuthUserMapper;
|
import com.nuaa.video.auth.mapper.AuthUserMapper;
|
||||||
import com.nuaa.video.auth.mapper.AuthUserRoleMapper;
|
import com.nuaa.video.auth.mapper.AuthUserRoleMapper;
|
||||||
import com.nuaa.video.auth.service.AuthUserService;
|
import com.nuaa.video.auth.service.AuthUserService;
|
||||||
@@ -21,6 +22,8 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -31,7 +34,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor //Lombok注解,会自动为final字段创建构造函数
|
||||||
public class AuthUserServiceImpl extends ServiceImpl<AuthUserMapper, AuthUser> implements AuthUserService {
|
public class AuthUserServiceImpl extends ServiceImpl<AuthUserMapper, AuthUser> implements AuthUserService {
|
||||||
|
|
||||||
private final AuthUserMapper authUserMapper;
|
private final AuthUserMapper authUserMapper;
|
||||||
@@ -39,6 +42,7 @@ public class AuthUserServiceImpl extends ServiceImpl<AuthUserMapper, AuthUser> i
|
|||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final JwtUtils jwtUtils;
|
private final JwtUtils jwtUtils;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
|
private final UserClient userClient;
|
||||||
|
|
||||||
@Value("${jwt.expiration:86400}")
|
@Value("${jwt.expiration:86400}")
|
||||||
private long expiration;
|
private long expiration;
|
||||||
@@ -171,6 +175,22 @@ public class AuthUserServiceImpl extends ServiceImpl<AuthUserMapper, AuthUser> i
|
|||||||
userRole.setCreateTime(LocalDateTime.now());
|
userRole.setCreateTime(LocalDateTime.now());
|
||||||
authUserRoleMapper.insert(userRole);
|
authUserRoleMapper.insert(userRole);
|
||||||
|
|
||||||
|
// 使用事务同步器确保在事务提交后再创建用户资料
|
||||||
|
final Long userId = user.getId();
|
||||||
|
final String username = user.getUsername();
|
||||||
|
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
|
try {
|
||||||
|
userClient.createUserInfo(userId, username);
|
||||||
|
log.info("用户资料创建成功:userId={}", userId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建用户资料失败,用户ID: " + userId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return Result.success("注册成功");
|
return Result.success("注册成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,22 @@ spring:
|
|||||||
username: ${MYSQL_USERNAME:root}
|
username: ${MYSQL_USERNAME:root}
|
||||||
password: ${MYSQL_PASSWORD:sty@G458221}
|
password: ${MYSQL_PASSWORD:sty@G458221}
|
||||||
hikari:
|
hikari:
|
||||||
minimum-idle: 5
|
# 减少最大生命周期,避免超过数据库的wait_timeout
|
||||||
maximum-pool-size: 20
|
max-lifetime: 58000 # 设置为MySQL wait_timeout的值减少一些,默认通常为8小时(28800000ms)
|
||||||
idle-timeout: 30000
|
# 空闲连接超时时间
|
||||||
max-lifetime: 1200000
|
idle-timeout: 300000 # 5分钟,超过该时间的空闲连接将被移除
|
||||||
connection-timeout: 30000
|
# 连接超时时间
|
||||||
|
connection-timeout: 20000 # 20秒
|
||||||
|
# 测试连接的有效性
|
||||||
connection-test-query: SELECT 1
|
connection-test-query: SELECT 1
|
||||||
|
# 保持连接活跃的时间间隔
|
||||||
|
keepalive-time: 60000 # 1分钟,定期发送ping保持连接
|
||||||
|
# 验证超时时间
|
||||||
|
validation-timeout: 3000 # 3秒
|
||||||
|
# 最小空闲连接数
|
||||||
|
minimum-idle: 5
|
||||||
|
# 最大连接池大小
|
||||||
|
maximum-pool-size: 20
|
||||||
|
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
@@ -87,4 +97,13 @@ logging:
|
|||||||
com.nuaa.video: info
|
com.nuaa.video: info
|
||||||
org.springframework.security: info
|
org.springframework.security: info
|
||||||
org.springframework.beans: debug
|
org.springframework.beans: debug
|
||||||
org.springframework.context: debug
|
org.springframework.context: debug
|
||||||
|
|
||||||
|
feign:
|
||||||
|
circuitbreaker:
|
||||||
|
enabled: true # 启用断路器
|
||||||
|
client:
|
||||||
|
config:
|
||||||
|
default:
|
||||||
|
connectTimeout: 5000
|
||||||
|
readTimeout: 5000
|
||||||
@@ -12,10 +12,135 @@
|
|||||||
<artifactId>video-user</artifactId>
|
<artifactId>video-user</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- 引入公共模块 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.nuaa.video</groupId>
|
<groupId>com.nuaa.video</groupId>
|
||||||
<artifactId>video-common</artifactId>
|
<artifactId>video-common</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba Nacos Discovery -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba Nacos Config -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Actuator -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Validation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud OpenFeign -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba Sentinel -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MySQL Connector -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis Plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
<version>3.5.11</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Redis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Hutool工具包 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.26</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud LoadBalancer -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
package com.nuaa.video.user;
|
package com.nuaa.video.user;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户服务启动类
|
||||||
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
|
@EnableFeignClients
|
||||||
|
@MapperScan("com.nuaa.video.user.mapper")
|
||||||
public class UserApplication {
|
public class UserApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(UserApplication.class, args);
|
SpringApplication.run(UserApplication.class, args);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.nuaa.video.user.config;
|
||||||
|
|
||||||
|
import feign.Retryer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class FeignConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Retryer feignRetryer() {
|
||||||
|
// 禁用重试
|
||||||
|
return Retryer.NEVER_RETRY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.nuaa.video.user.config;
|
||||||
|
|
||||||
|
import com.nuaa.video.common.utils.MinioUtils;
|
||||||
|
import io.minio.MinioClient;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MinIO配置类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "minio")
|
||||||
|
public class MinioConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务地址
|
||||||
|
*/
|
||||||
|
private String endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问密钥
|
||||||
|
*/
|
||||||
|
private String accessKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密钥
|
||||||
|
*/
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储桶名称
|
||||||
|
*/
|
||||||
|
private String bucketName;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MinioClient minioClient() {
|
||||||
|
return MinioClient.builder()
|
||||||
|
.endpoint(endpoint)
|
||||||
|
.credentials(accessKey, secretKey)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MinioUtils minioUtils(MinioClient minioClient) {
|
||||||
|
return new MinioUtils(minioClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.nuaa.video.user.config;
|
||||||
|
|
||||||
|
import com.nuaa.video.user.filter.JwtAuthenticationFilter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全配置类
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.csrf(csrf -> csrf.disable())
|
||||||
|
.cors(cors -> cors.disable())
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers(
|
||||||
|
"/user/profile/public/**",
|
||||||
|
"/user/profile/init",
|
||||||
|
"/actuator/**",
|
||||||
|
"/error"
|
||||||
|
).permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.nuaa.video.user.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.service.UserFollowService;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户关注控制器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/follow")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserFollowController {
|
||||||
|
|
||||||
|
private final UserFollowService userFollowService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注用户
|
||||||
|
*/
|
||||||
|
@PostMapping("/{followId}")
|
||||||
|
public Result<Void> followUser(HttpServletRequest request, @PathVariable Long followId) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userFollowService.followUser(userId, followId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消关注
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{followId}")
|
||||||
|
public Result<Void> unfollowUser(HttpServletRequest request, @PathVariable Long followId) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userFollowService.unfollowUser(userId, followId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户关注列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/following/{userId}")
|
||||||
|
public Result<Page<UserInfo>> getFollowingList(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "10") Integer size) {
|
||||||
|
Page<UserInfo> pageParam = new Page<>(page, size);
|
||||||
|
return userFollowService.getFollowingList(userId, pageParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户粉丝列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/followers/{userId}")
|
||||||
|
public Result<Page<UserInfo>> getFollowerList(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "10") Integer size) {
|
||||||
|
Page<UserInfo> pageParam = new Page<>(page, size);
|
||||||
|
return userFollowService.getFollowerList(userId, pageParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否已关注
|
||||||
|
*/
|
||||||
|
@GetMapping("/check/{followId}")
|
||||||
|
public Result<Boolean> checkIsFollowed(HttpServletRequest request, @PathVariable Long followId) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userFollowService.checkIsFollowed(userId, followId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取用户ID
|
||||||
|
*/
|
||||||
|
private Long getUserIdFromRequest(HttpServletRequest request) {
|
||||||
|
Object userIdObj = request.getAttribute("userId");
|
||||||
|
if (userIdObj instanceof Long) {
|
||||||
|
return (Long) userIdObj;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.nuaa.video.user.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserMessageDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserMessage;
|
||||||
|
import com.nuaa.video.user.service.UserMessageService;
|
||||||
|
import com.nuaa.video.user.vo.UserMessageVO;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户私信控制器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/message")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserMessageController {
|
||||||
|
|
||||||
|
private final UserMessageService userMessageService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送私信
|
||||||
|
*/
|
||||||
|
@PostMapping
|
||||||
|
public Result<Void> sendMessage(HttpServletRequest request, @RequestBody @Valid UserMessageDTO messageDTO) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userMessageService.sendMessage(userId, messageDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取私信列表(按会话分组)
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result<Page<UserMessageVO>> getMessageList(
|
||||||
|
HttpServletRequest request,
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "10") Integer size) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
Page<UserMessage> pageParam = new Page<>(page, size);
|
||||||
|
return userMessageService.getMessageList(userId, pageParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取与指定用户的私信记录
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail/{targetId}")
|
||||||
|
public Result<Page<UserMessageVO>> getMessageDetail(
|
||||||
|
HttpServletRequest request,
|
||||||
|
@PathVariable Long targetId,
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "20") Integer size) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
Page<UserMessage> pageParam = new Page<>(page, size);
|
||||||
|
return userMessageService.getMessageDetail(userId, targetId, pageParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记私信为已读
|
||||||
|
*/
|
||||||
|
@PutMapping("/read/{senderId}")
|
||||||
|
public Result<Void> markMessageRead(HttpServletRequest request, @PathVariable Long senderId) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userMessageService.markMessageRead(userId, senderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取未读私信数量
|
||||||
|
*/
|
||||||
|
@GetMapping("/unread/count")
|
||||||
|
public Result<Integer> getUnreadMessageCount(HttpServletRequest request) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userMessageService.getUnreadMessageCount(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取用户ID
|
||||||
|
*/
|
||||||
|
private Long getUserIdFromRequest(HttpServletRequest request) {
|
||||||
|
Object userIdObj = request.getAttribute("userId");
|
||||||
|
if (userIdObj instanceof Long) {
|
||||||
|
return (Long) userIdObj;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
package com.nuaa.video.user.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.entity.UserNotification;
|
||||||
|
import com.nuaa.video.user.service.UserNotificationService;
|
||||||
|
import com.nuaa.video.user.vo.UserNotificationVO;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户通知控制器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/notification")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserNotificationController {
|
||||||
|
|
||||||
|
private final UserNotificationService userNotificationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通知列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result<Page<UserNotificationVO>> getNotificationList(
|
||||||
|
HttpServletRequest request,
|
||||||
|
@RequestParam(value = "type", required = false) Integer type,
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "10") Integer size) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
Page<UserNotification> pageParam = new Page<>(page, size);
|
||||||
|
return userNotificationService.getNotificationList(userId, type, pageParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记通知为已读
|
||||||
|
*/
|
||||||
|
@PutMapping("/read")
|
||||||
|
public Result<Void> markAllNotificationRead(HttpServletRequest request) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userNotificationService.markNotificationRead(userId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记指定通知为已读
|
||||||
|
*/
|
||||||
|
@PutMapping("/read/{notificationId}")
|
||||||
|
public Result<Void> markNotificationRead(HttpServletRequest request, @PathVariable Long notificationId) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userNotificationService.markNotificationRead(userId, notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取未读通知数量
|
||||||
|
*/
|
||||||
|
@GetMapping("/unread/count")
|
||||||
|
public Result<Integer> getUnreadNotificationCount(HttpServletRequest request) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userNotificationService.getUnreadNotificationCount(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取用户ID
|
||||||
|
*/
|
||||||
|
private Long getUserIdFromRequest(HttpServletRequest request) {
|
||||||
|
Object userIdObj = request.getAttribute("userId");
|
||||||
|
if (userIdObj instanceof Long) {
|
||||||
|
return (Long) userIdObj;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package com.nuaa.video.user.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserProfileDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.feign.AuthFeignClient;
|
||||||
|
import com.nuaa.video.user.service.UserInfoService;
|
||||||
|
import com.nuaa.video.user.vo.UserProfileVO;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户资料控制器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/profile")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserProfileController {
|
||||||
|
|
||||||
|
private final UserInfoService userInfoService;
|
||||||
|
private final AuthFeignClient authFeignClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化用户资料
|
||||||
|
*/
|
||||||
|
@PostMapping("/init")
|
||||||
|
public Result<Void> initUserInfo(@RequestParam("userId") Long userId,
|
||||||
|
@RequestParam("username") String username) {
|
||||||
|
return userInfoService.createUserInfo(userId, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户资料
|
||||||
|
*/
|
||||||
|
@GetMapping("/current")
|
||||||
|
public Result<UserProfileVO> getCurrentUserProfile(HttpServletRequest request) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户资料是否存在
|
||||||
|
UserInfo userInfo = userInfoService.getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
// 资料不存在,自动创建
|
||||||
|
String username = getUsernameFromRequest(request);
|
||||||
|
if (username == null) {
|
||||||
|
// 尝试从Auth服务获取用户名
|
||||||
|
try {
|
||||||
|
Result<Object> userResult = authFeignClient.getCurrentUser(
|
||||||
|
request.getHeader("Authorization"));
|
||||||
|
if (userResult.getCode() == 200 && userResult.getData() != null) {
|
||||||
|
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||||
|
username = (String) userData.get("username");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取用户信息失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username != null) {
|
||||||
|
userInfoService.createUserInfo(userId, username);
|
||||||
|
userInfo = userInfoService.getById(userId);
|
||||||
|
} else {
|
||||||
|
return Result.error("无法创建用户资料,用户信息不完整");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfoService.getUserProfile(userId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定用户资料
|
||||||
|
*/
|
||||||
|
@GetMapping("/public/{userId}")
|
||||||
|
public Result<UserProfileVO> getUserProfile(@PathVariable Long userId, HttpServletRequest request) {
|
||||||
|
Long currentUserId = getUserIdFromRequest(request);
|
||||||
|
return userInfoService.getUserProfile(userId, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户资料
|
||||||
|
*/
|
||||||
|
@PutMapping
|
||||||
|
public Result<Void> updateUserProfile(HttpServletRequest request, @RequestBody @Valid UserProfileDTO userProfileDTO) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userInfoService.updateUserProfile(userId, userProfileDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传头像
|
||||||
|
*/
|
||||||
|
@PostMapping("/avatar")
|
||||||
|
public Result<String> uploadAvatar(HttpServletRequest request, @RequestParam("file") MultipartFile file) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userInfoService.uploadAvatar(userId, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索用户
|
||||||
|
*/
|
||||||
|
@GetMapping("/search")
|
||||||
|
public Result<Page<UserInfo>> searchUsers(
|
||||||
|
@RequestParam(value = "keyword", required = false) String keyword,
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "10") Integer size) {
|
||||||
|
Page<UserInfo> pageParam = new Page<>(page, size);
|
||||||
|
return userInfoService.searchUsers(pageParam, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取用户名
|
||||||
|
*/
|
||||||
|
private String getUsernameFromRequest(HttpServletRequest request) {
|
||||||
|
Object usernameObj = request.getAttribute("username");
|
||||||
|
if (usernameObj instanceof String) {
|
||||||
|
return (String) usernameObj;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取用户ID
|
||||||
|
*/
|
||||||
|
private Long getUserIdFromRequest(HttpServletRequest request) {
|
||||||
|
Object userIdObj = request.getAttribute("userId");
|
||||||
|
if (userIdObj instanceof Long) {
|
||||||
|
return (Long) userIdObj;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.nuaa.video.user.controller;
|
||||||
|
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserSettingDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserSetting;
|
||||||
|
import com.nuaa.video.user.service.UserSettingService;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设置控制器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/setting")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserSettingController {
|
||||||
|
|
||||||
|
private final UserSettingService userSettingService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户设置
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
public Result<UserSetting> getUserSetting(HttpServletRequest request) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userSettingService.getUserSetting(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户设置
|
||||||
|
*/
|
||||||
|
@PutMapping
|
||||||
|
public Result<Void> updateUserSetting(HttpServletRequest request, @RequestBody @Valid UserSettingDTO settingDTO) {
|
||||||
|
Long userId = getUserIdFromRequest(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return Result.error("未获取到用户ID");
|
||||||
|
}
|
||||||
|
return userSettingService.updateUserSetting(userId, settingDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取用户ID
|
||||||
|
*/
|
||||||
|
private Long getUserIdFromRequest(HttpServletRequest request) {
|
||||||
|
Object userIdObj = request.getAttribute("userId");
|
||||||
|
if (userIdObj instanceof Long) {
|
||||||
|
return (Long) userIdObj;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.nuaa.video.user.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户私信DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserMessageDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "接收者ID不能为空")
|
||||||
|
private Long receiverId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "消息内容不能为空")
|
||||||
|
@Size(max = 500, message = "消息内容不能超过500字")
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.nuaa.video.user.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户资料DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserProfileDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
@Size(min = 2, max = 20, message = "昵称长度必须在2-20之间")
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别(0:未知,1:男,2:女)
|
||||||
|
*/
|
||||||
|
private Integer gender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生日
|
||||||
|
*/
|
||||||
|
private LocalDate birthday;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个性签名
|
||||||
|
*/
|
||||||
|
@Size(max = 200, message = "个性签名不能超过200字")
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地区
|
||||||
|
*/
|
||||||
|
@Size(max = 100, message = "地区不能超过100字")
|
||||||
|
private String region;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.nuaa.video.user.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设置DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserSettingDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐私等级(1:公开,2:仅关注,3:私密)
|
||||||
|
*/
|
||||||
|
private Integer privacyLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer commentNotify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer likeNotify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer followNotify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer systemNotify;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.nuaa.video.user.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户关注实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("user_follow")
|
||||||
|
public class UserFollow implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注用户ID
|
||||||
|
*/
|
||||||
|
private Long followId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package com.nuaa.video.user.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("user_info")
|
||||||
|
public class UserInfo implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.INPUT)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像URL
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别(0:未知,1:男,2:女)
|
||||||
|
*/
|
||||||
|
private Integer gender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生日
|
||||||
|
*/
|
||||||
|
private LocalDate birthday;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个性签名
|
||||||
|
*/
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地区
|
||||||
|
*/
|
||||||
|
private String region;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 粉丝数
|
||||||
|
*/
|
||||||
|
private Integer followerCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注数
|
||||||
|
*/
|
||||||
|
private Integer followingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频数
|
||||||
|
*/
|
||||||
|
private Integer videoCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获赞数
|
||||||
|
*/
|
||||||
|
private Integer likeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 硬币数
|
||||||
|
*/
|
||||||
|
private Integer coinCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏数
|
||||||
|
*/
|
||||||
|
private Integer favoriteCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放数
|
||||||
|
*/
|
||||||
|
private Long viewCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等级
|
||||||
|
*/
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VIP等级
|
||||||
|
*/
|
||||||
|
private Integer vipLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VIP到期时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime vipExpireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人
|
||||||
|
*/
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新人
|
||||||
|
*/
|
||||||
|
private String updatedBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否关注(非数据库字段)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Boolean isFollowed;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.nuaa.video.user.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户私信实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("user_message")
|
||||||
|
public class UserMessage implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者ID
|
||||||
|
*/
|
||||||
|
private Long senderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者ID
|
||||||
|
*/
|
||||||
|
private Long receiverId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已读(0:未读,1:已读)
|
||||||
|
*/
|
||||||
|
private Integer isRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者昵称(非数据库字段)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String senderNickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者头像(非数据库字段)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String senderAvatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者昵称(非数据库字段)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String receiverNickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者头像(非数据库字段)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String receiverAvatar;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.nuaa.video.user.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户通知实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("user_notification")
|
||||||
|
public class UserNotification implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知类型(1:点赞,2:评论,3:关注,4:系统通知)
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相关ID
|
||||||
|
*/
|
||||||
|
private Long relatedId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已读(0:未读,1:已读)
|
||||||
|
*/
|
||||||
|
private Integer isRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.nuaa.video.user.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设置实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("user_setting")
|
||||||
|
public class UserSetting implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐私等级(1:公开,2:仅关注,3:私密)
|
||||||
|
*/
|
||||||
|
private Integer privacyLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer commentNotify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer likeNotify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer followNotify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统通知(0:关闭,1:开启)
|
||||||
|
*/
|
||||||
|
private Integer systemNotify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人
|
||||||
|
*/
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新人
|
||||||
|
*/
|
||||||
|
private String updatedBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.nuaa.video.user.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户每日统计实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("user_stat_daily")
|
||||||
|
public class UserStatDaily implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计日期
|
||||||
|
*/
|
||||||
|
private LocalDate statDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增粉丝数
|
||||||
|
*/
|
||||||
|
private Integer followerIncrement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增关注数
|
||||||
|
*/
|
||||||
|
private Integer followingIncrement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增视频数
|
||||||
|
*/
|
||||||
|
private Integer videoIncrement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增获赞数
|
||||||
|
*/
|
||||||
|
private Integer likeIncrement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增硬币数
|
||||||
|
*/
|
||||||
|
private Integer coinIncrement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增收藏数
|
||||||
|
*/
|
||||||
|
private Integer favoriteIncrement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增播放数
|
||||||
|
*/
|
||||||
|
private Long viewIncrement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// com.nuaa.video.user.feign.AuthFeignClient.java
|
||||||
|
package com.nuaa.video.user.feign;
|
||||||
|
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证服务Feign客户端
|
||||||
|
*/
|
||||||
|
@FeignClient(name = "video-auth", path = "/auth")
|
||||||
|
public interface AuthFeignClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户信息
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/current")
|
||||||
|
Result<Object> getCurrentUser(@RequestHeader("Authorization") String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取用户名
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户名
|
||||||
|
*/
|
||||||
|
@GetMapping("/username/{userId}")
|
||||||
|
String getUsernameById(@PathVariable("userId") Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.nuaa.video.user.filter;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT认证过滤器(不依赖Feign)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Value("${jwt.secret:video-platform-secret-key-2024-nuaa}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
private static final String TOKEN_PREFIX = "Bearer ";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
// 从请求头中获取JWT令牌
|
||||||
|
String token = getJwtFromRequest(request);
|
||||||
|
|
||||||
|
// 如果令牌存在,则本地验证
|
||||||
|
if (StringUtils.hasText(token)) {
|
||||||
|
try {
|
||||||
|
// 验证令牌
|
||||||
|
Claims claims = Jwts.parser()
|
||||||
|
.verifyWith(getSigningKey())
|
||||||
|
.build()
|
||||||
|
.parseSignedClaims(token)
|
||||||
|
.getPayload();
|
||||||
|
|
||||||
|
// 从令牌中提取用户ID和用户名
|
||||||
|
Long userId = Long.parseLong(claims.getSubject());
|
||||||
|
String username = claims.get("username", String.class);
|
||||||
|
|
||||||
|
// 设置用户权限
|
||||||
|
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||||
|
|
||||||
|
// 创建认证对象
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(username, null, authorities);
|
||||||
|
|
||||||
|
// 设置额外信息
|
||||||
|
authentication.setDetails(userId);
|
||||||
|
|
||||||
|
// 设置认证信息到安全上下文
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
|
// 在请求属性中存储用户ID
|
||||||
|
request.setAttribute("userId", userId);
|
||||||
|
request.setAttribute("username", username);
|
||||||
|
|
||||||
|
log.debug("认证成功: 用户ID={}, 用户名={}", userId, username);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JWT验证失败: {}", e.getMessage());
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求头中提取JWT令牌
|
||||||
|
*/
|
||||||
|
private String getJwtFromRequest(HttpServletRequest request) {
|
||||||
|
String bearerToken = request.getHeader("Authorization");
|
||||||
|
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
|
||||||
|
return bearerToken.substring(TOKEN_PREFIX.length());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取签名密钥
|
||||||
|
*/
|
||||||
|
private SecretKey getSigningKey() {
|
||||||
|
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||||
|
return Keys.hmacShaKeyFor(keyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.nuaa.video.user.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.user.entity.UserFollow;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户关注Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserFollowMapper extends BaseMapper<UserFollow> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户关注列表
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 关注用户列表
|
||||||
|
*/
|
||||||
|
Page<UserInfo> selectFollowingList(Page<UserInfo> page, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户粉丝列表
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 粉丝用户列表
|
||||||
|
*/
|
||||||
|
Page<UserInfo> selectFollowerList(Page<UserInfo> page, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否已关注
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param followId 关注用户ID
|
||||||
|
* @return 是否已关注
|
||||||
|
*/
|
||||||
|
int checkIsFollowed(@Param("userId") Long userId, @Param("followId") Long followId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.nuaa.video.user.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserInfoMapper extends BaseMapper<UserInfo> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加关注数
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param count 增加数量
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int increaseFollowingCount(@Param("userId") Long userId, @Param("count") int count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加粉丝数
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param count 增加数量
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int increaseFollowerCount(@Param("userId") Long userId, @Param("count") int count);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.nuaa.video.user.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.user.entity.UserMessage;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户私信Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserMessageMapper extends BaseMapper<UserMessage> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询私信列表(按会话分组)
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 私信列表
|
||||||
|
*/
|
||||||
|
Page<UserMessage> selectMessageGroupList(Page<UserMessage> page, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询与指定用户的私信记录
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param targetId 目标用户ID
|
||||||
|
* @return 私信记录
|
||||||
|
*/
|
||||||
|
Page<UserMessage> selectMessageDetail(Page<UserMessage> page, @Param("userId") Long userId, @Param("targetId") Long targetId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记私信为已读
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param senderId 发送者ID
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int markMessageRead(@Param("userId") Long userId, @Param("senderId") Long senderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取未读私信数量
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 未读私信数量
|
||||||
|
*/
|
||||||
|
int countUnreadMessage(@Param("userId") Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.nuaa.video.user.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.nuaa.video.user.entity.UserNotification;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户通知Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserNotificationMapper extends BaseMapper<UserNotification> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记通知为已读
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param notificationId 通知ID,为空则标记所有
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int markNotificationRead(@Param("userId") Long userId, @Param("notificationId") Long notificationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取未读通知数量
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 未读通知数量
|
||||||
|
*/
|
||||||
|
int countUnreadNotification(@Param("userId") Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.nuaa.video.user.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.nuaa.video.user.entity.UserSetting;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设置Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserSettingMapper extends BaseMapper<UserSetting> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.nuaa.video.user.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.nuaa.video.user.entity.UserStatDaily;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户每日统计Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface UserStatDailyMapper extends BaseMapper<UserStatDaily> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.nuaa.video.user.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.entity.UserFollow;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户关注服务接口
|
||||||
|
*/
|
||||||
|
public interface UserFollowService extends IService<UserFollow> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注用户
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param followId 被关注用户ID
|
||||||
|
* @return 关注结果
|
||||||
|
*/
|
||||||
|
Result<Void> followUser(Long userId, Long followId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消关注
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param followId 被关注用户ID
|
||||||
|
* @return 取消结果
|
||||||
|
*/
|
||||||
|
Result<Void> unfollowUser(Long userId, Long followId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户关注列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param page 分页参数
|
||||||
|
* @return 关注列表
|
||||||
|
*/
|
||||||
|
Result<Page<UserInfo>> getFollowingList(Long userId, Page<UserInfo> page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户粉丝列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param page 分页参数
|
||||||
|
* @return 粉丝列表
|
||||||
|
*/
|
||||||
|
Result<Page<UserInfo>> getFollowerList(Long userId, Page<UserInfo> page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否已关注
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param followId 关注用户ID
|
||||||
|
* @return 是否已关注
|
||||||
|
*/
|
||||||
|
Result<Boolean> checkIsFollowed(Long userId, Long followId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.nuaa.video.user.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserProfileDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.vo.UserProfileVO;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息服务接口
|
||||||
|
*/
|
||||||
|
public interface UserInfoService extends IService<UserInfo> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户资料
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param currentUserId 当前登录用户ID,可为空
|
||||||
|
* @return 用户资料
|
||||||
|
*/
|
||||||
|
Result<UserProfileVO> getUserProfile(Long userId, Long currentUserId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户资料
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param userProfileDTO 用户资料DTO
|
||||||
|
* @return 更新结果
|
||||||
|
*/
|
||||||
|
Result<Void> updateUserProfile(Long userId, UserProfileDTO userProfileDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传头像
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param file 头像文件
|
||||||
|
* @return 头像URL
|
||||||
|
*/
|
||||||
|
Result<String> uploadAvatar(Long userId, MultipartFile file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用户信息(用户注册初始化)
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param username 用户名,用作初始昵称
|
||||||
|
* @return 创建结果
|
||||||
|
*/
|
||||||
|
Result<Void> createUserInfo(Long userId, String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户简要信息
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 简要信息
|
||||||
|
*/
|
||||||
|
Result<UserInfo> getBriefUserInfo(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户列表
|
||||||
|
*
|
||||||
|
* @param page 分页参数
|
||||||
|
* @param keyword 搜索关键词
|
||||||
|
* @return 用户列表
|
||||||
|
*/
|
||||||
|
Result<Page<UserInfo>> searchUsers(Page<UserInfo> page, String keyword);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.nuaa.video.user.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserMessageDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserMessage;
|
||||||
|
import com.nuaa.video.user.vo.UserMessageVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户私信服务接口
|
||||||
|
*/
|
||||||
|
public interface UserMessageService extends IService<UserMessage> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送私信
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param messageDTO 私信DTO
|
||||||
|
* @return 发送结果
|
||||||
|
*/
|
||||||
|
Result<Void> sendMessage(Long userId, UserMessageDTO messageDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取私信列表(按会话分组)
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param page 分页参数
|
||||||
|
* @return 私信列表
|
||||||
|
*/
|
||||||
|
Result<Page<UserMessageVO>> getMessageList(Long userId, Page<UserMessage> page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取与指定用户的私信记录
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param targetId 目标用户ID
|
||||||
|
* @param page 分页参数
|
||||||
|
* @return 私信记录
|
||||||
|
*/
|
||||||
|
Result<Page<UserMessageVO>> getMessageDetail(Long userId, Long targetId, Page<UserMessage> page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记私信为已读
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param senderId 发送者ID
|
||||||
|
* @return 标记结果
|
||||||
|
*/
|
||||||
|
Result<Void> markMessageRead(Long userId, Long senderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取未读私信数量
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 未读私信数量
|
||||||
|
*/
|
||||||
|
Result<Integer> getUnreadMessageCount(Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.nuaa.video.user.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.entity.UserNotification;
|
||||||
|
import com.nuaa.video.user.vo.UserNotificationVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户通知服务接口
|
||||||
|
*/
|
||||||
|
public interface UserNotificationService extends IService<UserNotification> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建通知
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param type 通知类型
|
||||||
|
* @param content 通知内容
|
||||||
|
* @param relatedId 相关ID
|
||||||
|
* @return 创建结果
|
||||||
|
*/
|
||||||
|
Result<Void> createNotification(Long userId, Integer type, String content, Long relatedId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户通知列表
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param type 通知类型,null为全部
|
||||||
|
* @param page 分页参数
|
||||||
|
* @return 通知列表
|
||||||
|
*/
|
||||||
|
Result<Page<UserNotificationVO>> getNotificationList(Long userId, Integer type, Page<UserNotification> page);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记通知为已读
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param notificationId 通知ID,为空则标记所有
|
||||||
|
* @return 标记结果
|
||||||
|
*/
|
||||||
|
Result<Void> markNotificationRead(Long userId, Long notificationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取未读通知数量
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 未读通知数量
|
||||||
|
*/
|
||||||
|
Result<Integer> getUnreadNotificationCount(Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.nuaa.video.user.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserSettingDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserSetting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设置服务接口
|
||||||
|
*/
|
||||||
|
public interface UserSettingService extends IService<UserSetting> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户设置
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户设置
|
||||||
|
*/
|
||||||
|
Result<UserSetting> getUserSetting(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户设置
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param settingDTO 设置DTO
|
||||||
|
* @return 更新结果
|
||||||
|
*/
|
||||||
|
Result<Void> updateUserSetting(Long userId, UserSettingDTO settingDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建默认设置(用户注册初始化)
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 创建结果
|
||||||
|
*/
|
||||||
|
Result<Void> createDefaultSetting(Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package com.nuaa.video.user.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.nuaa.video.common.model.enums.ResultCode;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.entity.UserFollow;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.mapper.UserFollowMapper;
|
||||||
|
import com.nuaa.video.user.mapper.UserInfoMapper;
|
||||||
|
import com.nuaa.video.user.service.UserFollowService;
|
||||||
|
import com.nuaa.video.user.service.UserNotificationService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户关注服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserFollowServiceImpl extends ServiceImpl<UserFollowMapper, UserFollow> implements UserFollowService {
|
||||||
|
|
||||||
|
private final UserFollowMapper userFollowMapper;
|
||||||
|
private final UserInfoMapper userInfoMapper;
|
||||||
|
private final UserNotificationService userNotificationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> followUser(Long userId, Long followId) {
|
||||||
|
// 验证用户和关注对象是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
UserInfo followUser = userInfoMapper.selectById(followId);
|
||||||
|
|
||||||
|
if (user == null || followUser == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不能关注自己
|
||||||
|
if (userId.equals(followId)) {
|
||||||
|
return Result.error("不能关注自己");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已关注
|
||||||
|
int count = userFollowMapper.checkIsFollowed(userId, followId);
|
||||||
|
if (count > 0) {
|
||||||
|
return Result.error("已经关注该用户");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建关注关系
|
||||||
|
UserFollow userFollow = new UserFollow();
|
||||||
|
userFollow.setUserId(userId);
|
||||||
|
userFollow.setFollowId(followId);
|
||||||
|
userFollow.setCreateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 保存关注记录
|
||||||
|
save(userFollow);
|
||||||
|
|
||||||
|
// 更新用户和被关注用户的关注数和粉丝数
|
||||||
|
userInfoMapper.increaseFollowingCount(userId, 1);
|
||||||
|
userInfoMapper.increaseFollowerCount(followId, 1);
|
||||||
|
|
||||||
|
// 创建关注通知
|
||||||
|
String content = user.getNickname() + " 关注了你";
|
||||||
|
userNotificationService.createNotification(followId, 3, content, userId);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> unfollowUser(Long userId, Long followId) {
|
||||||
|
// 验证用户和关注对象是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
UserInfo followUser = userInfoMapper.selectById(followId);
|
||||||
|
|
||||||
|
if (user == null || followUser == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已关注
|
||||||
|
int count = userFollowMapper.checkIsFollowed(userId, followId);
|
||||||
|
if (count == 0) {
|
||||||
|
return Result.error("未关注该用户");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除关注关系
|
||||||
|
lambdaUpdate()
|
||||||
|
.eq(UserFollow::getUserId, userId)
|
||||||
|
.eq(UserFollow::getFollowId, followId)
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
// 更新用户和被关注用户的关注数和粉丝数
|
||||||
|
userInfoMapper.increaseFollowingCount(userId, -1);
|
||||||
|
userInfoMapper.increaseFollowerCount(followId, -1);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<UserInfo>> getFollowingList(Long userId, Page<UserInfo> page) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询关注列表
|
||||||
|
Page<UserInfo> followingList = userFollowMapper.selectFollowingList(page, userId);
|
||||||
|
|
||||||
|
return Result.success(followingList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<UserInfo>> getFollowerList(Long userId, Page<UserInfo> page) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询粉丝列表
|
||||||
|
Page<UserInfo> followerList = userFollowMapper.selectFollowerList(page, userId);
|
||||||
|
|
||||||
|
return Result.success(followerList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Boolean> checkIsFollowed(Long userId, Long followId) {
|
||||||
|
// 验证用户和关注对象是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
UserInfo followUser = userInfoMapper.selectById(followId);
|
||||||
|
|
||||||
|
if (user == null || followUser == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已关注
|
||||||
|
int count = userFollowMapper.checkIsFollowed(userId, followId);
|
||||||
|
|
||||||
|
return Result.success(count > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package com.nuaa.video.user.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.nuaa.video.common.model.enums.ResultCode;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.common.utils.MinioUtils;
|
||||||
|
import com.nuaa.video.user.dto.UserProfileDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.mapper.UserFollowMapper;
|
||||||
|
import com.nuaa.video.user.mapper.UserInfoMapper;
|
||||||
|
import com.nuaa.video.user.service.UserInfoService;
|
||||||
|
import com.nuaa.video.user.service.UserSettingService;
|
||||||
|
import com.nuaa.video.user.vo.UserProfileVO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
|
||||||
|
|
||||||
|
private final UserInfoMapper userInfoMapper;
|
||||||
|
private final UserFollowMapper userFollowMapper;
|
||||||
|
private final UserSettingService userSettingService;
|
||||||
|
private final MinioUtils minioUtils;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<UserProfileVO> getUserProfile(Long userId, Long currentUserId) {
|
||||||
|
// 查询用户信息
|
||||||
|
UserInfo userInfo = getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为VO
|
||||||
|
UserProfileVO profileVO = new UserProfileVO();
|
||||||
|
BeanUtils.copyProperties(userInfo, profileVO);
|
||||||
|
|
||||||
|
// 检查是否关注(如果当前用户已登录)
|
||||||
|
if (currentUserId != null && !currentUserId.equals(userId)) {
|
||||||
|
int count = userFollowMapper.checkIsFollowed(currentUserId, userId);
|
||||||
|
profileVO.setIsFollowed(count > 0);
|
||||||
|
} else {
|
||||||
|
profileVO.setIsFollowed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(profileVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> updateUserProfile(Long userId, UserProfileDTO userProfileDTO) {
|
||||||
|
// 查询用户信息
|
||||||
|
UserInfo userInfo = getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
if (userProfileDTO.getNickname() != null) {
|
||||||
|
userInfo.setNickname(userProfileDTO.getNickname());
|
||||||
|
}
|
||||||
|
if (userProfileDTO.getGender() != null) {
|
||||||
|
userInfo.setGender(userProfileDTO.getGender());
|
||||||
|
}
|
||||||
|
if (userProfileDTO.getBirthday() != null) {
|
||||||
|
userInfo.setBirthday(userProfileDTO.getBirthday());
|
||||||
|
}
|
||||||
|
if (userProfileDTO.getSignature() != null) {
|
||||||
|
userInfo.setSignature(userProfileDTO.getSignature());
|
||||||
|
}
|
||||||
|
if (userProfileDTO.getRegion() != null) {
|
||||||
|
userInfo.setRegion(userProfileDTO.getRegion());
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo.setUpdatedBy(userId.toString());
|
||||||
|
userInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 保存更新
|
||||||
|
updateById(userInfo);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<String> uploadAvatar(Long userId, MultipartFile file) {
|
||||||
|
try {
|
||||||
|
// 查询用户信息
|
||||||
|
UserInfo userInfo = getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传头像
|
||||||
|
String avatarUrl = minioUtils.uploadImage(file);
|
||||||
|
|
||||||
|
// 更新用户头像
|
||||||
|
userInfo.setAvatar(avatarUrl);
|
||||||
|
userInfo.setUpdatedBy(userId.toString());
|
||||||
|
userInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
updateById(userInfo);
|
||||||
|
|
||||||
|
// 返回头像URL
|
||||||
|
return Result.success(minioUtils.getPreSignedUrl(avatarUrl));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("上传头像失败", e);
|
||||||
|
return Result.error("上传头像失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> createUserInfo(Long userId, String username) {
|
||||||
|
// 检查是否已存在
|
||||||
|
UserInfo existUser = getById(userId);
|
||||||
|
if (existUser != null) {
|
||||||
|
return Result.error("用户信息已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户信息
|
||||||
|
UserInfo userInfo = new UserInfo();
|
||||||
|
userInfo.setId(userId);
|
||||||
|
userInfo.setNickname(username);
|
||||||
|
userInfo.setAvatar("default/avatar.png"); // 默认头像
|
||||||
|
userInfo.setGender(0);
|
||||||
|
userInfo.setFollowerCount(0);
|
||||||
|
userInfo.setFollowingCount(0);
|
||||||
|
userInfo.setVideoCount(0);
|
||||||
|
userInfo.setLikeCount(0);
|
||||||
|
userInfo.setCoinCount(0);
|
||||||
|
userInfo.setFavoriteCount(0);
|
||||||
|
userInfo.setViewCount(0L);
|
||||||
|
userInfo.setLevel(1);
|
||||||
|
userInfo.setVipLevel(0);
|
||||||
|
userInfo.setCreatedBy("system");
|
||||||
|
userInfo.setUpdatedBy("system");
|
||||||
|
userInfo.setCreateTime(LocalDateTime.now());
|
||||||
|
userInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 保存用户信息
|
||||||
|
save(userInfo);
|
||||||
|
|
||||||
|
// 创建默认设置
|
||||||
|
userSettingService.createDefaultSetting(userId);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<UserInfo> getBriefUserInfo(Long userId) {
|
||||||
|
UserInfo userInfo = getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(),ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
return Result.success(userInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<UserInfo>> searchUsers(Page<UserInfo> page, String keyword) {
|
||||||
|
// 构建查询条件
|
||||||
|
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
if (keyword != null && !keyword.trim().isEmpty()) {
|
||||||
|
queryWrapper.like(UserInfo::getNickname, keyword)
|
||||||
|
.or()
|
||||||
|
.like(UserInfo::getSignature, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按等级降序,注册时间降序
|
||||||
|
queryWrapper.orderByDesc(UserInfo::getLevel)
|
||||||
|
.orderByDesc(UserInfo::getCreateTime);
|
||||||
|
|
||||||
|
// 执行分页查询
|
||||||
|
Page<UserInfo> resultPage = page(page, queryWrapper);
|
||||||
|
|
||||||
|
return Result.success(resultPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
package com.nuaa.video.user.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.nuaa.video.common.model.enums.ResultCode;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserMessageDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.entity.UserMessage;
|
||||||
|
import com.nuaa.video.user.mapper.UserInfoMapper;
|
||||||
|
import com.nuaa.video.user.mapper.UserMessageMapper;
|
||||||
|
import com.nuaa.video.user.service.UserMessageService;
|
||||||
|
import com.nuaa.video.user.vo.UserMessageVO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户私信服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserMessageServiceImpl extends ServiceImpl<UserMessageMapper, UserMessage> implements UserMessageService {
|
||||||
|
|
||||||
|
private final UserMessageMapper userMessageMapper;
|
||||||
|
private final UserInfoMapper userInfoMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> sendMessage(Long userId, UserMessageDTO messageDTO) {
|
||||||
|
// 验证发送者和接收者是否存在
|
||||||
|
UserInfo sender = userInfoMapper.selectById(userId);
|
||||||
|
UserInfo receiver = userInfoMapper.selectById(messageDTO.getReceiverId());
|
||||||
|
|
||||||
|
if (sender == null || receiver == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不能给自己发私信
|
||||||
|
if (userId.equals(messageDTO.getReceiverId())) {
|
||||||
|
return Result.error("不能给自己发私信");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建私信记录
|
||||||
|
UserMessage message = new UserMessage();
|
||||||
|
message.setSenderId(userId);
|
||||||
|
message.setReceiverId(messageDTO.getReceiverId());
|
||||||
|
message.setContent(messageDTO.getContent());
|
||||||
|
message.setIsRead(0);
|
||||||
|
message.setCreateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 保存私信
|
||||||
|
save(message);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<UserMessageVO>> getMessageList(Long userId, Page<UserMessage> page) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询私信列表
|
||||||
|
Page<UserMessage> messagePage = userMessageMapper.selectMessageGroupList(page, userId);
|
||||||
|
|
||||||
|
// 转换为VO对象
|
||||||
|
Page<UserMessageVO> resultPage = new Page<>();
|
||||||
|
BeanUtils.copyProperties(messagePage, resultPage, "records");
|
||||||
|
|
||||||
|
// 转换记录
|
||||||
|
resultPage.setRecords(messagePage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()));
|
||||||
|
|
||||||
|
return Result.success(resultPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<UserMessageVO>> getMessageDetail(Long userId, Long targetId, Page<UserMessage> page) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
UserInfo target = userInfoMapper.selectById(targetId);
|
||||||
|
|
||||||
|
if (user == null || target == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询私信记录
|
||||||
|
Page<UserMessage> messagePage = userMessageMapper.selectMessageDetail(page, userId, targetId);
|
||||||
|
|
||||||
|
// 标记私信为已读
|
||||||
|
markMessageRead(userId, targetId);
|
||||||
|
|
||||||
|
// 转换为VO对象
|
||||||
|
Page<UserMessageVO> resultPage = new Page<>();
|
||||||
|
BeanUtils.copyProperties(messagePage, resultPage, "records");
|
||||||
|
|
||||||
|
// 转换记录
|
||||||
|
resultPage.setRecords(messagePage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()));
|
||||||
|
|
||||||
|
return Result.success(resultPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> markMessageRead(Long userId, Long senderId) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记私信为已读
|
||||||
|
userMessageMapper.markMessageRead(userId, senderId);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Integer> getUnreadMessageCount(Long userId) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取未读私信数量
|
||||||
|
int count = userMessageMapper.countUnreadMessage(userId);
|
||||||
|
|
||||||
|
return Result.success(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将实体对象转换为VO对象
|
||||||
|
*/
|
||||||
|
private UserMessageVO convertToVO(UserMessage message) {
|
||||||
|
UserMessageVO vo = new UserMessageVO();
|
||||||
|
BeanUtils.copyProperties(message, vo);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
package com.nuaa.video.user.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.nuaa.video.common.model.enums.ResultCode;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.entity.UserNotification;
|
||||||
|
import com.nuaa.video.user.entity.UserSetting;
|
||||||
|
import com.nuaa.video.user.mapper.UserInfoMapper;
|
||||||
|
import com.nuaa.video.user.mapper.UserNotificationMapper;
|
||||||
|
import com.nuaa.video.user.mapper.UserSettingMapper;
|
||||||
|
import com.nuaa.video.user.service.UserNotificationService;
|
||||||
|
import com.nuaa.video.user.vo.UserNotificationVO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户通知服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserNotificationServiceImpl extends ServiceImpl<UserNotificationMapper, UserNotification> implements UserNotificationService {
|
||||||
|
|
||||||
|
private final UserNotificationMapper userNotificationMapper;
|
||||||
|
private final UserInfoMapper userInfoMapper;
|
||||||
|
private final UserSettingMapper userSettingMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> createNotification(Long userId, Integer type, String content, Long relatedId) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户通知设置
|
||||||
|
UserSetting setting = userSettingMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 根据设置决定是否发送通知
|
||||||
|
if (setting != null) {
|
||||||
|
boolean shouldNotify = false;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 1: // 点赞通知
|
||||||
|
shouldNotify = setting.getLikeNotify() == 1;
|
||||||
|
break;
|
||||||
|
case 2: // 评论通知
|
||||||
|
shouldNotify = setting.getCommentNotify() == 1;
|
||||||
|
break;
|
||||||
|
case 3: // 关注通知
|
||||||
|
shouldNotify = setting.getFollowNotify() == 1;
|
||||||
|
break;
|
||||||
|
case 4: // 系统通知
|
||||||
|
shouldNotify = setting.getSystemNotify() == 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
shouldNotify = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldNotify) {
|
||||||
|
return Result.success(); // 用户已关闭该类型通知
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建通知
|
||||||
|
UserNotification notification = new UserNotification();
|
||||||
|
notification.setUserId(userId);
|
||||||
|
notification.setType(type);
|
||||||
|
notification.setContent(content);
|
||||||
|
notification.setRelatedId(relatedId);
|
||||||
|
notification.setIsRead(0);
|
||||||
|
notification.setCreateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 保存通知
|
||||||
|
save(notification);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<UserNotificationVO>> getNotificationList(Long userId, Integer type, Page<UserNotification> page) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
LambdaQueryWrapper<UserNotification> queryWrapper = new LambdaQueryWrapper<UserNotification>()
|
||||||
|
.eq(UserNotification::getUserId, userId)
|
||||||
|
.eq(type != null, UserNotification::getType, type)
|
||||||
|
.orderByDesc(UserNotification::getCreateTime);
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
Page<UserNotification> notificationPage = page(page, queryWrapper);
|
||||||
|
|
||||||
|
// 转换为VO对象
|
||||||
|
Page<UserNotificationVO> resultPage = new Page<>();
|
||||||
|
BeanUtils.copyProperties(notificationPage, resultPage, "records");
|
||||||
|
|
||||||
|
// 转换记录
|
||||||
|
resultPage.setRecords(notificationPage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()));
|
||||||
|
|
||||||
|
return Result.success(resultPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> markNotificationRead(Long userId, Long notificationId) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记通知为已读
|
||||||
|
userNotificationMapper.markNotificationRead(userId, notificationId);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Integer> getUnreadNotificationCount(Long userId) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取未读通知数量
|
||||||
|
int count = userNotificationMapper.countUnreadNotification(userId);
|
||||||
|
|
||||||
|
return Result.success(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将实体对象转换为VO对象
|
||||||
|
*/
|
||||||
|
private UserNotificationVO convertToVO(UserNotification notification) {
|
||||||
|
UserNotificationVO vo = new UserNotificationVO();
|
||||||
|
BeanUtils.copyProperties(notification, vo);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
package com.nuaa.video.user.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.nuaa.video.common.model.enums.ResultCode;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.dto.UserSettingDTO;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.entity.UserSetting;
|
||||||
|
import com.nuaa.video.user.mapper.UserInfoMapper;
|
||||||
|
import com.nuaa.video.user.mapper.UserSettingMapper;
|
||||||
|
import com.nuaa.video.user.service.UserSettingService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户设置服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserSettingServiceImpl extends ServiceImpl<UserSettingMapper, UserSetting> implements UserSettingService {
|
||||||
|
|
||||||
|
private final UserSettingMapper userSettingMapper;
|
||||||
|
private final UserInfoMapper userInfoMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<UserSetting> getUserSetting(Long userId) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户设置
|
||||||
|
UserSetting setting = getOne(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
|
||||||
|
|
||||||
|
// 如果设置不存在,创建默认设置
|
||||||
|
if (setting == null) {
|
||||||
|
createDefaultSetting(userId);
|
||||||
|
setting = getOne(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> updateUserSetting(Long userId, UserSettingDTO settingDTO) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户设置
|
||||||
|
UserSetting setting = getOne(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
|
||||||
|
|
||||||
|
// 如果设置不存在,创建默认设置
|
||||||
|
if (setting == null) {
|
||||||
|
setting = new UserSetting();
|
||||||
|
setting.setUserId(userId);
|
||||||
|
setting.setCreatedBy(userId.toString());
|
||||||
|
setting.setCreateTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新设置
|
||||||
|
if (settingDTO.getPrivacyLevel() != null) {
|
||||||
|
setting.setPrivacyLevel(settingDTO.getPrivacyLevel());
|
||||||
|
}
|
||||||
|
if (settingDTO.getCommentNotify() != null) {
|
||||||
|
setting.setCommentNotify(settingDTO.getCommentNotify());
|
||||||
|
}
|
||||||
|
if (settingDTO.getLikeNotify() != null) {
|
||||||
|
setting.setLikeNotify(settingDTO.getLikeNotify());
|
||||||
|
}
|
||||||
|
if (settingDTO.getFollowNotify() != null) {
|
||||||
|
setting.setFollowNotify(settingDTO.getFollowNotify());
|
||||||
|
}
|
||||||
|
if (settingDTO.getSystemNotify() != null) {
|
||||||
|
setting.setSystemNotify(settingDTO.getSystemNotify());
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.setUpdatedBy(userId.toString());
|
||||||
|
setting.setUpdateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 保存设置
|
||||||
|
saveOrUpdate(setting);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> createDefaultSetting(Long userId) {
|
||||||
|
// 验证用户是否存在
|
||||||
|
UserInfo user = userInfoMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error(ResultCode.USER_NOT_FOUND.getCode(), ResultCode.USER_NOT_FOUND.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已存在设置
|
||||||
|
Long count = count(new LambdaQueryWrapper<UserSetting>().eq(UserSetting::getUserId, userId));
|
||||||
|
if (count > 0) {
|
||||||
|
return Result.success(); // 已存在设置,不需要创建
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建默认设置
|
||||||
|
UserSetting setting = new UserSetting();
|
||||||
|
setting.setUserId(userId);
|
||||||
|
setting.setPrivacyLevel(1); // 公开
|
||||||
|
setting.setCommentNotify(1); // 开启
|
||||||
|
setting.setLikeNotify(1); // 开启
|
||||||
|
setting.setFollowNotify(1); // 开启
|
||||||
|
setting.setSystemNotify(1); // 开启
|
||||||
|
setting.setCreatedBy("system");
|
||||||
|
setting.setUpdatedBy("system");
|
||||||
|
setting.setCreateTime(LocalDateTime.now());
|
||||||
|
setting.setUpdateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 保存设置
|
||||||
|
save(setting);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.nuaa.video.user.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户私信VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserMessageVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者ID
|
||||||
|
*/
|
||||||
|
private Long senderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者昵称
|
||||||
|
*/
|
||||||
|
private String senderNickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送者头像
|
||||||
|
*/
|
||||||
|
private String senderAvatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者ID
|
||||||
|
*/
|
||||||
|
private Long receiverId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者昵称
|
||||||
|
*/
|
||||||
|
private String receiverNickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收者头像
|
||||||
|
*/
|
||||||
|
private String receiverAvatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已读(0:未读,1:已读)
|
||||||
|
*/
|
||||||
|
private Integer isRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.nuaa.video.user.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户通知VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserNotificationVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知类型(1:点赞,2:评论,3:关注,4:系统通知)
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相关ID
|
||||||
|
*/
|
||||||
|
private Long relatedId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已读(0:未读,1:已读)
|
||||||
|
*/
|
||||||
|
private Integer isRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.nuaa.video.user.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户资料VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UserProfileVO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像URL
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别(0:未知,1:男,2:女)
|
||||||
|
*/
|
||||||
|
private Integer gender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生日
|
||||||
|
*/
|
||||||
|
private LocalDate birthday;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个性签名
|
||||||
|
*/
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地区
|
||||||
|
*/
|
||||||
|
private String region;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 粉丝数
|
||||||
|
*/
|
||||||
|
private Integer followerCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注数
|
||||||
|
*/
|
||||||
|
private Integer followingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频数
|
||||||
|
*/
|
||||||
|
private Integer videoCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获赞数
|
||||||
|
*/
|
||||||
|
private Integer likeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等级
|
||||||
|
*/
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VIP等级
|
||||||
|
*/
|
||||||
|
private Integer vipLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VIP到期时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime vipExpireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已关注
|
||||||
|
*/
|
||||||
|
private Boolean isFollowed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
dynamic:
|
||||||
|
primary: master
|
||||||
|
strict: false
|
||||||
|
datasource:
|
||||||
|
master:
|
||||||
|
url: jdbc:mysql://5.180.253.115:3306/video_user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
|
username: root
|
||||||
|
password: sty@G458221
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
database: 1
|
||||||
|
host: 5.180.253.115
|
||||||
|
port: 6379
|
||||||
|
password: redis_EsJ8FX
|
||||||
|
timeout: 10000
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.nuaa.video: debug
|
||||||
|
org.springframework.security: debug
|
||||||
@@ -4,30 +4,92 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: video-user
|
name: video-user
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- optional:nacos:${spring.application.name}.yaml
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: 5.180.253.115:8848
|
server-addr: ${NACOS_HOST:5.180.253.115}:${NACOS_PORT:8848}
|
||||||
namespace: public
|
namespace: ${NACOS_NAMESPACE:public}
|
||||||
group: DEFAULT_GROUP
|
group: ${NACOS_GROUP:DEFAULT_GROUP}
|
||||||
|
config:
|
||||||
|
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||||
|
namespace: ${spring.cloud.nacos.discovery.namespace}
|
||||||
|
group: ${spring.cloud.nacos.discovery.group}
|
||||||
|
file-extension: yaml
|
||||||
sentinel:
|
sentinel:
|
||||||
transport:
|
transport:
|
||||||
dashboard: 5.180.253.115:8080
|
dashboard: ${SENTINEL_DASHBOARD_HOST:5.180.253.115}:${SENTINEL_DASHBOARD_PORT:8080}
|
||||||
port: 8721
|
port: 8721
|
||||||
eager: true
|
eager: true
|
||||||
|
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:mysql://5.180.253.115:3306/video_user?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
|
||||||
username: root
|
|
||||||
password: sty@G458221
|
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://${MYSQL_HOST:5.180.253.115}:${MYSQL_PORT:3306}/video_user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
|
username: ${MYSQL_USERNAME:root}
|
||||||
|
password: ${MYSQL_PASSWORD:sty@G458221}
|
||||||
|
hikari:
|
||||||
|
minimum-idle: 5
|
||||||
|
maximum-pool-size: 20
|
||||||
|
idle-timeout: 30000
|
||||||
|
max-lifetime: 1200000
|
||||||
|
connection-timeout: 30000
|
||||||
|
connection-test-query: SELECT 1
|
||||||
|
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 5.180.253.115
|
host: ${REDIS_HOST:5.180.253.115}
|
||||||
port: 6379
|
port: ${REDIS_PORT:6379}
|
||||||
password: redis_EsJ8FX
|
password: ${REDIS_PASSWORD:redis_EsJ8FX}
|
||||||
database: 1
|
database: 1
|
||||||
|
timeout: 10000
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 200
|
||||||
|
max-wait: -1
|
||||||
|
max-idle: 10
|
||||||
|
min-idle: 0
|
||||||
|
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
type-aliases-package: com.nuaa.video.user.entity
|
||||||
configuration:
|
configuration:
|
||||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
map-underscore-to-camel-case: true
|
||||||
|
cache-enabled: false
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
global-config:
|
||||||
|
db-config:
|
||||||
|
id-type: auto
|
||||||
|
logic-delete-field: is_deleted
|
||||||
|
logic-delete-value: 1
|
||||||
|
logic-not-delete-value: 0
|
||||||
|
|
||||||
|
auth:
|
||||||
|
ignore-urls:
|
||||||
|
- /user/profile/public/**
|
||||||
|
- /user/profile/init
|
||||||
|
- /actuator/**
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: '*'
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
show-details: always
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.nuaa.video: info
|
||||||
|
org.springframework.security: info
|
||||||
|
|
||||||
|
# MinIO配置
|
||||||
|
minio:
|
||||||
|
endpoint: http://5.180.253.115:9002
|
||||||
|
accessKey: minioadmin
|
||||||
|
secretKey: minioadmin
|
||||||
|
bucketName: video-platform
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.nuaa.video.user.mapper.UserFollowMapper">
|
||||||
|
|
||||||
|
<!-- 查询用户关注列表 -->
|
||||||
|
<select id="selectFollowingList" resultType="com.nuaa.video.user.entity.UserInfo">
|
||||||
|
SELECT u.*
|
||||||
|
FROM user_follow f
|
||||||
|
JOIN user_info u ON f.follow_id = u.id
|
||||||
|
WHERE f.user_id = #{userId}
|
||||||
|
ORDER BY f.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询用户粉丝列表 -->
|
||||||
|
<select id="selectFollowerList" resultType="com.nuaa.video.user.entity.UserInfo">
|
||||||
|
SELECT u.*
|
||||||
|
FROM user_follow f
|
||||||
|
JOIN user_info u ON f.user_id = u.id
|
||||||
|
WHERE f.follow_id = #{userId}
|
||||||
|
ORDER BY f.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 检查是否已关注 -->
|
||||||
|
<select id="checkIsFollowed" resultType="int">
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM user_follow
|
||||||
|
WHERE user_id = #{userId} AND follow_id = #{followId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.nuaa.video.user.mapper.UserInfoMapper">
|
||||||
|
|
||||||
|
<!-- 增加关注数 -->
|
||||||
|
<update id="increaseFollowingCount">
|
||||||
|
UPDATE user_info
|
||||||
|
SET following_count = following_count + #{count}
|
||||||
|
WHERE id = #{userId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 增加粉丝数 -->
|
||||||
|
<update id="increaseFollowerCount">
|
||||||
|
UPDATE user_info
|
||||||
|
SET follower_count = follower_count + #{count}
|
||||||
|
WHERE id = #{userId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.nuaa.video.user.mapper.UserMessageMapper">
|
||||||
|
|
||||||
|
<!-- 查询私信列表(按会话分组) -->
|
||||||
|
<select id="selectMessageGroupList" resultType="com.nuaa.video.user.entity.UserMessage">
|
||||||
|
SELECT
|
||||||
|
m.*,
|
||||||
|
u1.nickname as sender_nickname,
|
||||||
|
u1.avatar as sender_avatar,
|
||||||
|
u2.nickname as receiver_nickname,
|
||||||
|
u2.avatar as receiver_avatar
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
MAX(id) as id,
|
||||||
|
IF(sender_id = #{userId}, receiver_id, sender_id) as chat_user_id,
|
||||||
|
MAX(create_time) as latest_time
|
||||||
|
FROM
|
||||||
|
user_message
|
||||||
|
WHERE
|
||||||
|
sender_id = #{userId} OR receiver_id = #{userId}
|
||||||
|
GROUP BY
|
||||||
|
chat_user_id
|
||||||
|
ORDER BY
|
||||||
|
latest_time DESC
|
||||||
|
) as t
|
||||||
|
JOIN user_message m ON t.id = m.id
|
||||||
|
JOIN user_info u1 ON m.sender_id = u1.id
|
||||||
|
JOIN user_info u2 ON m.receiver_id = u2.id
|
||||||
|
ORDER BY m.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询与指定用户的私信记录 -->
|
||||||
|
<select id="selectMessageDetail" resultType="com.nuaa.video.user.entity.UserMessage">
|
||||||
|
SELECT
|
||||||
|
m.*,
|
||||||
|
u1.nickname as sender_nickname,
|
||||||
|
u1.avatar as sender_avatar,
|
||||||
|
u2.nickname as receiver_nickname,
|
||||||
|
u2.avatar as receiver_avatar
|
||||||
|
FROM
|
||||||
|
user_message m
|
||||||
|
JOIN user_info u1 ON m.sender_id = u1.id
|
||||||
|
JOIN user_info u2 ON m.receiver_id = u2.id
|
||||||
|
WHERE
|
||||||
|
(m.sender_id = #{userId} AND m.receiver_id = #{targetId})
|
||||||
|
OR
|
||||||
|
(m.sender_id = #{targetId} AND m.receiver_id = #{userId})
|
||||||
|
ORDER BY
|
||||||
|
m.create_time ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 标记私信为已读 -->
|
||||||
|
<update id="markMessageRead">
|
||||||
|
UPDATE user_message
|
||||||
|
SET is_read = 1
|
||||||
|
WHERE receiver_id = #{userId} AND sender_id = #{senderId} AND is_read = 0
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 获取未读私信数量 -->
|
||||||
|
<select id="countUnreadMessage" resultType="int">
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM user_message
|
||||||
|
WHERE receiver_id = #{userId} AND is_read = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.nuaa.video.user.mapper.UserNotificationMapper">
|
||||||
|
|
||||||
|
<!-- 标记通知为已读 -->
|
||||||
|
<update id="markNotificationRead">
|
||||||
|
UPDATE user_notification
|
||||||
|
SET is_read = 1
|
||||||
|
WHERE user_id = #{userId}
|
||||||
|
<if test="notificationId != null">
|
||||||
|
AND id = #{notificationId}
|
||||||
|
</if>
|
||||||
|
AND is_read = 0
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<!-- 获取未读通知数量 -->
|
||||||
|
<select id="countUnreadNotification" resultType="int">
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM user_notification
|
||||||
|
WHERE user_id = #{userId} AND is_read = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user