<file_sep>//
//  AppManager.swift
//  Mood
//
//  Created by Hascats on 2021/6/19.
//

import Foundation
import Combine

struct AppManager {
    
    static let Authenticated = PassthroughSubject<Bool, Never>()
    
    static func IsAuthenticated() -> Bool {
        return UserDefaults.standard.string(forKey: "token") != nil
    }
}
<file_sep>/root/.virtualenvs/ios/bin/python /root/workspace/iOSproject/crawler.py<file_sep>//
//  ContentView.swift
//  Mood
//
//  Created by Hascats on 2021/6/18.
//

import SwiftUI
struct Result:Codable{
    let id : Int
    let labels: String
    let url : String
}

struct Response:Codable{
    
    let results : [Result]
}


struct ContentView: View {
    
    @State var isAuthenticated = AppManager.IsAuthenticated()
    @State var results : [Result] = []

    
    
    var body: some View {
        TabView{
            NavigationView{
                ScrollView(.vertical){
                    ForEach(self.results,id:\.id){ item in
                        LazyVStack{
                            URLImage(withURL: item.url)
                            HStack{
                                Text(item.labels)
                            }.frame(width: 300, height: 40, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                            .background(Color.gray)
                            
                        }
                    }
                }.navigationBarTitle(Text("Mood"))
            }
            .tabItem {
                Image(systemName: "photo.on.rectangle")
                Text("Mood")
            }
            
            
            
            Group {
                isAuthenticated ? AnyView(HomeView()) : AnyView(LoginView())
            }
            .onReceive(AppManager.Authenticated, perform: {
                isAuthenticated = $0
            }).tabItem {
                Image(systemName: "person.crop.circle")
                Text("Login")
            }
        }
        .onAppear(perform: request_data)

    }
    
    func request_data(){
        guard let url = URL(string:"https://www.hascats.cn")
            else { return }
            URLSession.shared.dataTask(with: url){ (data,response,error) in
                guard let data = data,
                      let decodedData = try? JSONDecoder().decode(Response.self,from: data)
                else { return }
                
                self.results = decodedData.results
                
            }.resume()
    }

}
 









struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
<file_sep>//
//  LoginView.swift
//  Mood
//
//  Created by Hascats on 2021/6/19.
//

import SwiftUI
import Combine

struct LoginView: View {
    @ObservedObject var vm = LoginViewModel()
    @State var showAlert = false
    
    var body: some View {
        VStack {
            Text("Login")
                .font(.largeTitle)
                .bold()
                .padding(.bottom, 40)
            
            VStack {
                TextField("Email", text: $vm.email)
                    .keyboardType(.emailAddress)
                    .textContentType(.emailAddress)
                    .autocapitalization(.none)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
            }
            .padding()
            
            VStack {
                SecureField("Password", text: $vm.password)
                    .textContentType(.password)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
            }
            .padding()
            
            Button("Login") {
                showAlert = !vm.loginUser()
            }
            .padding()
            .frame(width: 100, height: 40, alignment: .center)
            .background(Color.green)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text(vm.errorMessage))
        })
    }
}

struct LoginView_Previews: PreviewProvider {
    static var previews: some View {
        LoginView()
    }
}
<file_sep>//
//  HomeViewModel.swift
//  Mood
//
//  Created by Hascats on 2021/6/19.
//

import Foundation
import Combine

class HomeViewModel: ObservableObject {
    func logoutUser() {
        UserDefaults.standard.removeObject(forKey: "token")
        AppManager.Authenticated.send(false)
    }
}
<file_sep># Mood
主要思路：
  1. iOS端App通过网络请求，请求服务器
  2. 服务器查询云数据库，获得图片的相关信息(id,url)等
  3. 服务器以通过HTTP Web应用返回JSON数据，包含以上信息
  4. App通过图片的url获取图片，并进行渲染
  5. 图片的url信息由服务器上的爬虫程序定时爬取(每天凌晨三点)，保证图片的更新
### 路径解释：
  - /Mood 包含App源代码
  - /server：
    - crawer.py 爬虫程序
    - server.py web服务程序
    - nohup.out 程序输出
    - paged.txt 辅助爬虫程序的一个文件
    - run_crawler.sh 爬虫程序启动脚本，由服务器定时任务程序cronb执行
<file_sep>import pymysql
from typing import *
from parsel import Selector
import httpx
import asyncio
import random
import pickle

class Spider():
    def __init__(self,page) -> None:
        self.headers = {"User-Agent":"Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19"}
        self.path = 'spider_downloads'
        self.url = 'https://www.wallpapermaiden.com/category/anime?page=' + str(page)
        self.download_urls = list(tuple())
        self.page = page

    def connect_db(self):
        conn = pymysql.connect(
        host='bj-cynosdbmysql-grp-rjihok1e.sql.tencentcdb.com',
        port=21280,
        user='root',
        password='<PASSWORD>',
        db='mood',
        charset='utf8',
       # autocommit=True,    # 如果插入数据，， 是否自动提交? 和conn.commit()功能一致。
        )
        return conn

    async def __get_download_url__(self,url):
        async with httpx.AsyncClient() as client:
            response = await client.get(url,headers=self.headers)
            download_url = Selector(response.text).css('.wpBig > a::attr(href)').get()
            self.download_urls.append(download_url)


    def get_urls(self):
        response = httpx.get(self.url)
        selector = Selector(response.text)
        wallpaper_list = selector.css('.wallpaperList')
        urls = wallpaper_list.css('.wallpaperBgImage > img::attr(src)').getall()
        labels = wallpaper_list.css('.wallpaperBgImage > img::attr(alt)').getall()
        info = list(zip(labels,map(lambda url:'https://www.wallpapermaiden.com'+url,urls)))
        self.download_urls = info
        # loop = asyncio.get_event_loop()
        # tasks = [
        #     self.__get_download_url__(url)\
        #     for url in urls
        # ]
        # loop.run_until_complete(asyncio.wait(tasks))
        # loop.close()
        
    

    async def parse_single_page(self,url):
        async with httpx.AsyncClient() as client:
            conn = self.connect_db()
            cur = conn.cursor()
            insert_sqli = f"insert into images(liked,labels,url) values(0,'{url[0]}','{url[1]}');"
            cur.execute(insert_sqli)
            conn.commit()
            cur.close()
            conn.close()
            

    def crawl(self)->None:
        try:
            print(f"爬取第{self.page}页的图片")
            self.get_urls()
            loop = asyncio.get_event_loop()
            tasks = [
                self.parse_single_page(url)\
                for url in self.download_urls
            ]
            loop.run_until_complete(asyncio.wait(tasks))
            loop.close()
        except Exception as e:
            print(e)


if __name__ == '__main__':
    page = random.randint(2,955)
    ls = []
    with open("paged.txt","rb") as f:
        ls = pickle.load(f)
    while(page in ls):
        page = random.randint(2,955)
    spider = Spider(page)
    spider.crawl()
    print(f"爬取第{page}页成功!")
    ls.append(page)
    with open("paged.txt","wb") as f:
        pickle.dump(ls,f)

    <file_sep># -*- coding: UTF-8 -*-
from flask import Flask
import pymysql
app = Flask(__name__)

@app.get("/")
def read_item():
    conn = pymysql.connect(
        host='bj-cynosdbmysql-grp-rjihok1e.sql.tencentcdb.com',
        port=21280,
        user='root',
        password='<PASSWORD>',
        db='mood',
        charset='utf8',
       # autocommit=True,    # 如果插入数据，， 是否自动提交? 和conn.commit()功能一致。
    )
    cur = conn.cursor()
    sqli = "select id,labels,url from images order by time desc limit 10;"
    result = cur.execute(sqli)  # 默认不返回查询结果集， 返回数据记录数。
    info = cur.fetchall()     # 3). 获取所有的查询结果
    info = list(map(lambda tup: {"id":tup[0],"labels":tup[1],"url":tup[2]},info))
    info = {"results":info}
    cur.close()
    conn.close()
    return info





if __name__ == '__main__':
    app.run( host="0.0.0.0", port=443,debug=False,ssl_context=('/root/workspace/lqw/5581010_www.hascats.cn.pem','/root/workspace/lqw/5581010_www.hascats.cn.key'))

<file_sep>//
//  HomeView.swift
//  Mood
//
//  Created by Hascats on 2021/6/19.
//

import SwiftUI

struct HomeView: View {
    @ObservedObject var vm = HomeViewModel()
    
    var body: some View {
        VStack {
            Text("Welcome")
                .font(.largeTitle)
            
            Spacer()
                .frame(height: 100)
            
            Button("Logout") {
                vm.logoutUser()
            }
            .padding()
            .frame(width: 100, height: 40, alignment: .center)
            .background(Color.green)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}
<file_sep>//
//  LoginViewModel.swift
//  Mood
//
//  Created by Hascats on 2021/6/19.
//

import SwiftUI
import Combine

class LoginViewModel: ObservableObject {
    @Published var email = ""
    @Published var password = ""
    
    var errorMessage = ""
}

extension LoginViewModel {
    func loginUser() -> Bool {
        if email == "<EMAIL>",
           password == "<PASSWORD>" {
            let token = UUID().uuidString
            UserDefaults.standard.set(token, forKey: "token")
            AppManager.Authenticated.send(true)
            
            return true
        } else {
            errorMessage = "Invalid credentials"
            return false
        }
    }
}
