diff --git a/internal/application/article/service.go b/internal/application/article/service.go index 34199b6..6d71535 100644 --- a/internal/application/article/service.go +++ b/internal/application/article/service.go @@ -84,35 +84,37 @@ func (a *ArticleService) Find(ePhone string, page *page.Page, searchParams map[s if err != nil { a.log.Error(err) } - columnData, err := a.purchaseRepo.FindColumnsByUserId(user.Id) - columnMap := make(map[uint64]struct{}) - if len(columnData) != 0 { - for _, v := range columnData { - columnMap[v.ContentId] = struct{}{} + if user != nil { + columnData, err := a.purchaseRepo.FindColumnsByUserId(user.Id) + columnMap := make(map[uint64]struct{}) + if len(columnData) != 0 { + for _, v := range columnData { + columnMap[v.ContentId] = struct{}{} + } } - } - articleIds := make([]uint64, 0, len(articles)) - for _, v := range articles { - _, has := columnMap[v.Type] - if has { - purchaseId[v.Id] = struct{}{} - } else { - articleIds = append(articleIds, v.Id) + articleIds := make([]uint64, 0, len(articles)) + for _, v := range articles { + _, has := columnMap[v.Type] + if has { + purchaseId[v.Id] = struct{}{} + } else { + articleIds = append(articleIds, v.Id) + } } - } - purchaseData, err := a.purchaseRepo.FindArticleById(user.Id, articleIds...) - articleIds = nil - if err != nil { - a.log.Error(err.Error()) - } - if purchaseData != nil { - for _, v := range purchaseData { - purchaseId[v.ContentId] = struct{}{} + purchaseData, err := a.purchaseRepo.FindArticleById(user.Id, articleIds...) + articleIds = nil + if err != nil { + a.log.Error(err.Error()) + } + if purchaseData != nil { + for _, v := range purchaseData { + purchaseId[v.ContentId] = struct{}{} + } } - } - articleIds = nil + articleIds = nil + } } @@ -152,6 +154,9 @@ func (a *ArticleService) FindUnLock(ePhone string, page *page.Page, searchParams a.log.Error(err) return err } + if user == nil { + return errors.New("未找到用户") + } conds := make([]builder.Cond, 0) class := searchParams["search_eq_class"] if class != "" { @@ -251,6 +256,10 @@ func (a *ArticleService) Detail(userPhone string, id uint64) (*ArticleDto, error return nil, err } + if user == nil { + return nil, errors.New("未找到用户") + } + p, err := a.purchaseRepo.FindByUserIdAndContent(user.Id, id, purchase.ContentTypeArticle) if err != nil { a.log.Error(err) diff --git a/internal/application/auth/captcha_service.go b/internal/application/auth/captcha_service.go index 59d3446..addadda 100644 --- a/internal/application/auth/captcha_service.go +++ b/internal/application/auth/captcha_service.go @@ -234,6 +234,18 @@ func (c *CaptchaService) GenerateSmsCaptcha(username string, phone string) (*Sms return nil, errors.New("手机号不能为空") } + user, err := c.userService.GetUserInfoByPhone(phone) + if err != nil { + c.log.Error(err) + return nil, err + } + + if user != nil { + if user.Status == 0 { + return nil, errors.New("您当前暂时不能登录,请联系管理员!") + } + } + // 1. 验证手机号格式 if err := c.VerifyPhoneValid(phone); err != nil { return nil, err diff --git a/internal/application/column/service.go b/internal/application/column/service.go index 1ed63d7..9ec8897 100644 --- a/internal/application/column/service.go +++ b/internal/application/column/service.go @@ -80,24 +80,6 @@ func (s *Service) GetColumn(ePhone string, name string) (*ColumnDto, error) { Unlock: unlock, CreatedAt: time.Time{}, } - if ePhone == "" { - return columnDto, nil - } - userData, err := s.userRepo.FindByPhone(ePhone) - if err != nil { - s.log.Error(err.Error()) - return columnDto, nil - } - purchaseData, err := s.purchaseRepo.FindColumnById(userData.Id, col.ID) - if err != nil { - s.log.Error(err) - return columnDto, nil - } - if len(purchaseData) != 0 { - if purchaseData[0].ContentId == col.ID { - columnDto.Unlock = true - } - } return columnDto, nil } diff --git a/internal/application/user/service.go b/internal/application/user/service.go index a7f9612..685e01a 100644 --- a/internal/application/user/service.go +++ b/internal/application/user/service.go @@ -102,16 +102,46 @@ func (u *UserService) FindLoginUser(username string) (*UserDto, error) { }, nil } -func (u *UserService) GetUserProfileByPhone(ePhone string) (*UserDto, error) { +func (u *UserService) GetUserProfileByePhone(ePhone string) (*UserDto, error) { user, err := u.repo.FindByPhone(ePhone) if err != nil { u.log.Error(err) return nil, err } + if user == nil { + return nil, errors.New("未找到用户") + } p, err := u.phoneEncryption.StringPhone(user.Phone) if err != nil { u.log.Error(err) + return nil, err + } + return &UserDto{ + Uid: user.Id, + Username: user.Username, + Phone: p, + Password: user.Password != "", + Status: user.Status, + GiftCount: user.GiftCount, + }, nil +} + +func (u *UserService) GetUserInfoByPhone(phone string) (*UserDto, error) { + ePhone, err := u.phoneEncryption.Encrypt(phone) + if err != nil { + u.log.Error(err) + return nil, err + } + p, _ := u.phoneEncryption.StringPhone(ePhone) + user, err := u.repo.FindByPhone(ePhone) + if err != nil { + u.log.Error(err) + return nil, err + } + if user == nil { + return nil, nil } + return &UserDto{ Uid: user.Id, Username: user.Username, diff --git a/internal/domain/user/repository.go b/internal/domain/user/repository.go index 9de983f..d57097f 100644 --- a/internal/domain/user/repository.go +++ b/internal/domain/user/repository.go @@ -26,7 +26,7 @@ type UserRepository interface { FindByID(id uint64) (*User, error) // FindByPhone 根据手机号查找用户 - FindByPhone(phone string) (*User, error) + FindByPhone(ePhone string) (*User, error) // FindAll 查询用户列表 FindAll(page *page.Page, conds []builder.Cond) error diff --git a/internal/infrastructure/persistence/auth/auth_repo.go b/internal/infrastructure/persistence/auth/auth_repo.go index 4861acc..7ed8b71 100644 --- a/internal/infrastructure/persistence/auth/auth_repo.go +++ b/internal/infrastructure/persistence/auth/auth_repo.go @@ -7,6 +7,7 @@ import ( "cls/pkg/logger" "cls/pkg/xorm_engine" "errors" + "fmt" "xorm.io/builder" ) @@ -63,11 +64,17 @@ func (a AuthRepositoryORM) LoginByCaptcha(phone string) (string, error) { //注册用户 u.Phone = phone u.GiftCount = 2 + u.Status = 1 _, err = a.engine.Insert(u) if err != nil { a.log.Error(err) } } + + if u.Status == 0 { + return "", errors.New(fmt.Sprintf("用户【%d】禁止登录", u.Id)) + } + token, err := a.auth.GenerateUserToken(phone) if err != nil { a.log.Error(err) diff --git a/internal/infrastructure/persistence/user/user_repo.go b/internal/infrastructure/persistence/user/user_repo.go index 8893458..e396231 100644 --- a/internal/infrastructure/persistence/user/user_repo.go +++ b/internal/infrastructure/persistence/user/user_repo.go @@ -10,7 +10,7 @@ import ( "xorm.io/builder" ) -//UserRepositoryORM ORM 实现UserRepository 持久化数据 +// UserRepositoryORM ORM 实现UserRepository 持久化数据 type UserRepositoryORM struct { engine *xorm_engine.Engine log logger.Logger @@ -68,14 +68,14 @@ func (u *UserRepositoryORM) FindByID(id uint64) (*domainUser.User, error) { return user, nil } -func (u *UserRepositoryORM) FindByPhone(phone string) (*domainUser.User, error) { +func (u *UserRepositoryORM) FindByPhone(ePhone string) (*domainUser.User, error) { user := &domainUser.User{} - exist, err := u.engine.Where(builder.Eq{"phone": phone}).Get(user) + exist, err := u.engine.Where(builder.Eq{"phone": ePhone}).Get(user) if err != nil { return nil, err } if !exist { - return nil, errors.New("用户不存在") + return nil, nil } return user, nil diff --git a/internal/interfaces/auth/auth_handler.go b/internal/interfaces/auth/auth_handler.go index 0861e0e..a6ae788 100644 --- a/internal/interfaces/auth/auth_handler.go +++ b/internal/interfaces/auth/auth_handler.go @@ -9,8 +9,6 @@ import ( jwt "github.com/appleboy/gin-jwt/v2" "github.com/gin-gonic/gin" "net/http" - - auth_middleware "cls/internal/infrastructure/middleware/auth" ) type AuthHandler struct { @@ -97,32 +95,6 @@ func (h *AuthHandler) GetImageCaptcha(c *gin.Context) { c.JSON(http.StatusOK, captcha) } -// VerifySmsCaptcha 验证短信验证码 -func (h *AuthHandler) VerifySmsCaptcha(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "验证成功"}) -} - -func (h *AuthHandler) VerifyImageCaptcha(c *gin.Context) { - var req struct { - Token string `json:"token" binding:"required"` - X int `json:"x" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "参数不完整"}) - return - } - - // 1. 先验证图片验证码 - if b, err := h.captchaService.VerifySlide(req.Token, req.X); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "图片验证码错误"}) - return - } else { - c.JSON(http.StatusOK, b) - } - -} - func (h *AuthHandler) LoginCaptcha(c *gin.Context) { var req struct { Phone string `json:"phone" binding:"required"` @@ -194,34 +166,5 @@ func (h *AuthHandler) RegisterRouters(app gin.IRouter) { auth.POST("/login-password", h.LoginPassword) auth.POST("/image-captcha", h.GetImageCaptcha) auth.POST("/sms-captcha", h.GetSmsCaptcha) - auth.POST("/verify-image", h.VerifyImageCaptcha) - auth.POST("/verify-sms", h.VerifySmsCaptcha) - auth.POST("/decode-token", h.DecodeToken) } } - -// DecodeToken 解析 token 并返回原始的加密手机号 -func (h *AuthHandler) DecodeToken(c *gin.Context) { - var req struct { - Token string `json:"token" binding:"required"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "参数不完整"}) - return - } - - // 通过 auth 模块获取密钥 - secretKey := h.service.GetJWTSecretKey() - - // 调用 DecodeTokenStatic 方法解析 token - encryptedPhone, err := auth_middleware.DecodeTokenStatic(req.Token, []byte(secretKey)) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "encryptedPhone": encryptedPhone, - }) -} diff --git a/internal/interfaces/user/user_handler.go b/internal/interfaces/user/user_handler.go index c20937f..17fa93e 100644 --- a/internal/interfaces/user/user_handler.go +++ b/internal/interfaces/user/user_handler.go @@ -105,7 +105,7 @@ func (u *UserHandler) getProfile(c *gin.Context) { c.AbortWithStatus(http.StatusInternalServerError) return } - userInfo, err := u.service.GetUserProfileByPhone(ePhone) + userInfo, err := u.service.GetUserProfileByePhone(ePhone) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 816fcfb..622bea3 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -23,6 +23,6 @@ export class AppComponent implements OnInit { private shouldShowTabs(url: string): boolean { // 在首页和我的页面显示底部导航栏 - return url === '/home' || url === '/mine' || url === '/mine/login'; + return url=== '/' || url === '/home' || url === '/mine' || url === '/mine/login'; } } diff --git a/ui/src/app/home/article-buy/article-buy.page.ts b/ui/src/app/home/article-buy/article-buy.page.ts index 187bd5c..aab97a1 100644 --- a/ui/src/app/home/article-buy/article-buy.page.ts +++ b/ui/src/app/home/article-buy/article-buy.page.ts @@ -45,12 +45,14 @@ export class ArticleBuyPage implements OnInit { } loadCoupons(){ this.homeService.getCouponList().subscribe(res=>{ - res.forEach(res=>{ - if(this.getDiscountPrice(this.price.amount,this.price.discount) > res.minAmount){ - res.canUse = true - } - }) - this.coupons = res; + if(res) { + res.forEach(res=>{ + if(this.getDiscountPrice(this.price.amount,this.price.discount) > res.minAmount){ + res.canUse = true + } + }) + this.coupons = res; + } }) } getDiscountPrice(originalPrice: number,discount:number): number { diff --git a/ui/src/app/home/article-detail/article-detail.page.html b/ui/src/app/home/article-detail/article-detail.page.html index 0dc85b2..3d30e26 100644 --- a/ui/src/app/home/article-detail/article-detail.page.html +++ b/ui/src/app/home/article-detail/article-detail.page.html @@ -29,7 +29,19 @@
相关个股: - {{ article?.stocks != '' ? article?.stocks : '暂无' }} + + + + {{stock.trim()}} + + {{formatChange(stockChanges[stock.trim()])}} + + , + + + 暂无 +
路诚声明: diff --git a/ui/src/app/home/article-detail/article-detail.page.scss b/ui/src/app/home/article-detail/article-detail.page.scss index 0cc7e49..3d897b8 100644 --- a/ui/src/app/home/article-detail/article-detail.page.scss +++ b/ui/src/app/home/article-detail/article-detail.page.scss @@ -76,6 +76,19 @@ ion-back-button { font-size: 12px; color: #1D1E22; font-weight: 500; + + .change { + margin-left: 4px; + font-weight: 500; + + &.up { + color: #F93034; + } + + &.down { + color: #07A50B; + } + } } } diff --git a/ui/src/app/home/article-detail/article-detail.page.ts b/ui/src/app/home/article-detail/article-detail.page.ts index efef111..e0cb48b 100644 --- a/ui/src/app/home/article-detail/article-detail.page.ts +++ b/ui/src/app/home/article-detail/article-detail.page.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router'; import { ModalController } from '@ionic/angular'; import { Article } from "../../shared/model/article"; import {ImagePreviewComponent} from "./image-preview/image-preview.component"; - +import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-article-detail', @@ -15,11 +15,13 @@ import {ImagePreviewComponent} from "./image-preview/image-preview.component"; export class ArticleDetailPage implements OnInit, AfterViewInit { article?: Article; weekday = ""; + stockChanges: { [key: string]: number } = {}; constructor( private navCtrl: NavController, private router: Router, - private modalCtrl: ModalController + private modalCtrl: ModalController, + private http: HttpClient ) { // 获取导航传递的数据 const navigation = this.router.getCurrentNavigation(); @@ -27,6 +29,7 @@ export class ArticleDetailPage implements OnInit, AfterViewInit { if (article) { this.article = article; this.addWeekday(); + this.getStockChanges(); } } @@ -85,4 +88,35 @@ export class ArticleDetailPage implements OnInit, AfterViewInit { modal.present(); }); } + + private getStockChanges() { + if (!this.article?.stocks) return; + + const stockCodes = this.article.stocks.split(',').map(code => code.trim()); + + stockCodes.forEach(code => { + if (code) { + // 调用接口时使用纯数字代码 + const numericCode = code.replace(/^(sz|sh)/, ''); + this.http.get(`http://api.mairui.club/hsrl/ssjy/${numericCode}/13988152-0AB6-41A9-B2CC-DC9B50250113`) + .subscribe((data: any) => { + if (data && data.pc) { + this.stockChanges[code] = data.pc; + } + }); + } + }); + } + + getStockChangeClass(code: string): string { + const change = this.stockChanges[code]; + if (change > 0) return 'up'; + if (change < 0) return 'down'; + return ''; + } + + formatChange(change: number): string { + if (change > 0) return `+${change}%`; + return `${change}%`; + } } diff --git a/ui/src/app/home/component/article-content/article-content.component.html b/ui/src/app/home/component/article-content/article-content.component.html index db1fb49..7668a8d 100644 --- a/ui/src/app/home/component/article-content/article-content.component.html +++ b/ui/src/app/home/component/article-content/article-content.component.html @@ -1,12 +1,12 @@ - + 最新 - - 已解锁 + + 已解锁 - - 免费试读 + + 免费试读 @@ -22,9 +22,13 @@

请登录后查看

- - + + +
+ +

暂无已解锁文章

+
diff --git a/ui/src/app/home/component/article-content/article-content.component.ts b/ui/src/app/home/component/article-content/article-content.component.ts index 540bd10..706e7a5 100644 --- a/ui/src/app/home/component/article-content/article-content.component.ts +++ b/ui/src/app/home/component/article-content/article-content.component.ts @@ -5,6 +5,7 @@ import {HomeService} from "../../home.service"; import {getGiftCount, getUser} from "../../../mine/mine.service"; import {Subject, Subscription} from "rxjs"; import {NavigationEnd, Router} from "@angular/router"; +import {takeUntil} from "rxjs/operators"; @Component({ selector: 'app-article-content', @@ -18,11 +19,13 @@ export class ArticleContentComponent implements OnInit { size: 10, search_eq_class:"" }; - hasMore: boolean = true; + hasMore: boolean = false; @Input() className:string = "" username:string = "" data:Article[] = [] currentSelect = "new" + selectBtn:string = "new" + private destroy$ = new Subject(); constructor(private homeService:HomeService, private cdr:ChangeDetectorRef) { } @@ -32,13 +35,21 @@ export class ArticleContentComponent implements OnInit { this.getUsername(); } ionChange(e:any){ + } + select(v:string) { + if(v==this.selectBtn){ + return + } + this.hasMore = false; + this.selectBtn = v this.searchParams['page'] = 0 this.data = [] - this.currentSelect = e.detail.value + this.destroy$.next() this.getData() } getData() { + switch (this.currentSelect){ case "new": return this.getNewData() @@ -60,12 +71,14 @@ export class ArticleContentComponent implements OnInit { } getNewData() { - this.homeService.list(this.searchParams).subscribe(res=>{ - if(res.items.length > 0) { - this.data = this.data.concat(res.items) - this.searchParams['page'] = this.searchParams['page']+1; - this.hasMore = res.items.length === this.searchParams['size']; - this.cdr.detectChanges(); + this.homeService.list(this.searchParams).pipe(takeUntil(this.destroy$)).subscribe(res=>{ + if(res.items){ + if(res.items.length > 0) { + this.data = this.data.concat(res.items) + this.searchParams['page'] = this.searchParams['page']+1; + this.hasMore = res.items.length === this.searchParams['size']; + this.cdr.detectChanges(); + } } }) } @@ -75,24 +88,28 @@ export class ArticleContentComponent implements OnInit { if(this.className != "") { this.searchParams['search_eq_class'] = this.className } - this.homeService.unlockList(this.searchParams).subscribe(res=>{ - if(res.items.length > 0) { - this.data = this.data.concat(res.items) - this.searchParams['page'] = this.searchParams['page']+1; - this.hasMore = res.items.length === this.searchParams['size']; - this.cdr.detectChanges(); + this.homeService.unlockList(this.searchParams).pipe(takeUntil(this.destroy$)).subscribe(res=>{ + if(res.items) { + if(res.items.length > 0) { + this.data = this.data.concat(res.items) + this.searchParams['page'] = this.searchParams['page']+1; + this.hasMore = res.items.length === this.searchParams['size']; + this.cdr.detectChanges(); + } } }) } } getFreeData() { - this.homeService.freeList(this.searchParams).subscribe(res=>{ - if(res.items.length > 0) { - this.data = this.data.concat(res.items) - this.searchParams['page'] = this.searchParams['page']+1; - this.hasMore = res.items.length === this.searchParams['size']; - this.cdr.detectChanges(); + this.homeService.freeList(this.searchParams).pipe(takeUntil(this.destroy$)).subscribe(res=>{ + if(res.items) { + if(res.items.length > 0) { + this.data = this.data.concat(res.items) + this.searchParams['page'] = this.searchParams['page']+1; + this.hasMore = res.items.length === this.searchParams['size']; + this.cdr.detectChanges(); + } } }) } diff --git a/ui/src/app/home/component/home-icons/home-icons.component.html b/ui/src/app/home/component/home-icons/home-icons.component.html index 9162816..d29fc25 100644 --- a/ui/src/app/home/component/home-icons/home-icons.component.html +++ b/ui/src/app/home/component/home-icons/home-icons.component.html @@ -4,13 +4,13 @@
- +
- +
- {{icon.name}} + {{icon.name}}
diff --git a/ui/src/app/mine/mine.page.html b/ui/src/app/mine/mine.page.html index 4b2d89f..6b66e70 100644 --- a/ui/src/app/mine/mine.page.html +++ b/ui/src/app/mine/mine.page.html @@ -17,6 +17,12 @@ 我的优惠券 +
+ +

暂无优惠券

+
+ +
diff --git a/ui/src/app/mine/mine.page.scss b/ui/src/app/mine/mine.page.scss index 6429eef..fc21237 100644 --- a/ui/src/app/mine/mine.page.scss +++ b/ui/src/app/mine/mine.page.scss @@ -111,7 +111,7 @@ ion-segment { --padding-start: 16px; --inner-padding-end: 16px; --background: transparent; - + ion-label { h2 { font-size: 16px; @@ -192,7 +192,7 @@ ion-item.image-captcha-item { color: #ff4d4f; display: flex; align-items: baseline; - + .symbol { font-size: 14px; } @@ -241,14 +241,36 @@ ion-footer { --padding-bottom: 8px; --border-top:none; // --background: #fff; - + } - + .logout-button { padding: 0 16px; - + ion-button { margin: 0; } } } + + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + text-align: center; + + ion-icon { + font-size: 48px; + color: #999; + margin-bottom: 16px; + } + + p { + color: #999; + font-size: 14px; + margin: 0; + } +} diff --git a/ui/src/app/mine/mine.page.ts b/ui/src/app/mine/mine.page.ts index c3bbe2f..a0682be 100644 --- a/ui/src/app/mine/mine.page.ts +++ b/ui/src/app/mine/mine.page.ts @@ -60,7 +60,6 @@ export class MinePage implements OnInit, OnDestroy { checkLoginStatus() { getUser().subscribe(res=>{ - console.log(res) if(res.username == "") { this.isLoggedIn = false; this.navCtrl.navigateForward("/mine/login") diff --git a/ui/src/assets/ewm.png b/ui/src/assets/ewm.png index c193e93..39c8e87 100644 Binary files a/ui/src/assets/ewm.png and b/ui/src/assets/ewm.png differ